Skip to content

Commit 72915b8

Browse files
authored
Merge pull request rust-lang#21 from Chris-Hawblitzel/debugger
Add preliminary support for AIR-level model reconstruction
2 parents 8302a32 + dc98763 commit 72915b8

21 files changed

+477
-237
lines changed

verify/air/src/ast.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::rc::Rc;
23

34
pub type RawSpan = Rc<dyn std::any::Any>;
@@ -11,15 +12,11 @@ pub type SpanOption = Rc<Option<Span>>;
1112

1213
pub type TypeError = String;
1314

14-
#[derive(Debug)]
15-
pub enum ValidityResult {
16-
Valid,
17-
Invalid(SpanOption, SpanOption),
18-
TypeError(TypeError),
19-
}
20-
2115
pub type Ident = Rc<String>;
2216

17+
pub(crate) type Snapshot = HashMap<Ident, u32>;
18+
pub(crate) type Snapshots = HashMap<Ident, Snapshot>;
19+
2320
pub type Typ = Rc<TypX>;
2421
pub type Typs = Rc<Vec<Typ>>;
2522
#[derive(Debug)]

verify/air/src/context.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::ast::{Command, CommandX, Decl, Ident, Query, SpanOption, TypeError, ValidityResult};
1+
use crate::ast::{Command, CommandX, Decl, Ident, Query, SpanOption, TypeError};
2+
use crate::model::Model;
23
use crate::print_parse::Logger;
34
use crate::typecheck::Typing;
45
use std::collections::{HashMap, HashSet};
@@ -13,6 +14,13 @@ pub(crate) struct AssertionInfo {
1314
pub(crate) decl: Decl,
1415
}
1516

17+
#[derive(Debug)]
18+
pub enum ValidityResult<'a> {
19+
Valid,
20+
Invalid(Model<'a>, SpanOption, SpanOption),
21+
TypeError(TypeError),
22+
}
23+
1624
pub struct Context<'ctx> {
1725
pub(crate) context: &'ctx z3::Context,
1826
pub(crate) solver: &'ctx z3::Solver<'ctx>,
@@ -22,6 +30,7 @@ pub struct Context<'ctx> {
2230
pub(crate) assert_infos: HashMap<Ident, Rc<AssertionInfo>>,
2331
pub(crate) assert_infos_count: u64,
2432
pub(crate) typing: Typing,
33+
pub(crate) debug: bool,
2534
pub(crate) rlimit: u32,
2635
pub(crate) air_initial_log: Logger,
2736
pub(crate) air_middle_log: Logger,
@@ -47,6 +56,7 @@ impl<'ctx> Context<'ctx> {
4756
funs: HashMap::new(),
4857
snapshots: HashSet::new(),
4958
},
59+
debug: false,
5060
rlimit: 0,
5161
air_initial_log: Logger::new(None),
5262
air_middle_log: Logger::new(None),
@@ -72,6 +82,14 @@ impl<'ctx> Context<'ctx> {
7282
self.smt_log = Logger::new(Some(writer));
7383
}
7484

85+
pub fn set_debug(&mut self, debug: bool) {
86+
self.debug = debug;
87+
}
88+
89+
pub fn get_debug(&self) -> bool {
90+
self.debug
91+
}
92+
7593
pub fn set_rlimit(&mut self, rlimit: u32) {
7694
self.rlimit = rlimit;
7795
self.air_initial_log.log_set_option("rlimit", &rlimit.to_string());
@@ -219,12 +237,12 @@ impl<'ctx> Context<'ctx> {
219237
if let Err(err) = crate::typecheck::check_query(self, query) {
220238
return ValidityResult::TypeError(err);
221239
}
222-
let query = crate::var_to_const::lower_query(query);
240+
let (query, snapshots, local_vars) = crate::var_to_const::lower_query(query);
223241
self.air_middle_log.log_query(&query);
224242
let query = crate::block_to_assert::lower_query(&query);
225243
self.air_final_log.log_query(&query);
226244

227-
let validity = crate::smt_verify::smt_check_query(self, &query);
245+
let validity = crate::smt_verify::smt_check_query(self, &query, snapshots, local_vars);
228246

229247
validity
230248
}

verify/air/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
pub mod ast;
22
pub mod ast_util;
33
pub mod context;
4+
pub mod model;
45

56
#[macro_use]
67
pub mod print_parse;
78

89
mod block_to_assert;
910
mod def;
11+
mod smt_util;
1012
mod smt_verify;
1113
mod tests;
1214
mod typecheck;

verify/air/src/main.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use air::ast::{CommandX, Span, ValidityResult};
2-
use air::context::Context;
1+
use air::ast::{CommandX, Span};
2+
use air::context::{Context, ValidityResult};
33
use getopts::Options;
44
use sise::Node;
55
use std::fs::File;
@@ -14,6 +14,7 @@ pub fn main() {
1414
opts.optopt("", "log-air-middle", "Log AIR queries in middle form", "FILENAME");
1515
opts.optopt("", "log-air-final", "Log AIR queries in final form", "FILENAME");
1616
opts.optopt("", "log-smt", "Log SMT queries", "FILENAME");
17+
opts.optflag("d", "debug", "Debug verification failures");
1718
opts.optflag("h", "help", "print this help menu");
1819

1920
let print_usage = || {
@@ -77,6 +78,8 @@ pub fn main() {
7778
let z3_context = z3::Context::new(&z3_config);
7879
let z3_solver = z3::Solver::new(&z3_context);
7980
let mut air_context = Context::new(&z3_context, &z3_solver);
81+
let debug = matches.opt_present("debug");
82+
air_context.set_debug(debug);
8083

8184
// Start logging
8285
if let Some(filename) = matches.opt_str("log-air-middle") {
@@ -106,7 +109,7 @@ pub fn main() {
106109
ValidityResult::TypeError(err) => {
107110
panic!("Type error: {}", err);
108111
}
109-
ValidityResult::Invalid(span1, span2) => {
112+
ValidityResult::Invalid(m, span1, span2) => {
110113
count_errors += 1;
111114
match &*span1 {
112115
None => {
@@ -124,6 +127,9 @@ pub fn main() {
124127
println!("Additional error detail at {}", as_string);
125128
}
126129
}
130+
if debug {
131+
println!("Model: {}", m);
132+
}
127133
}
128134
}
129135
}

verify/air/src/model.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//! Provides an AIR-level interface to the model returned by the SMT solver
2+
//! when it reaches a SAT conclusion
3+
4+
use crate::ast::{Decl, DeclX, Ident, Snapshots};
5+
use crate::context::Context;
6+
use crate::smt_util::new_const;
7+
use std::collections::HashMap;
8+
use std::fmt;
9+
use z3::ast::Dynamic;
10+
11+
#[derive(Debug)]
12+
/// AIR-level model of a concrete counterexample
13+
pub struct Model<'a> {
14+
/// Handle to the original Z3 model; only for internal use, e.g., for `eval`
15+
z3_model: z3::Model<'a>,
16+
/// Internal mapping of snapshot IDs to snapshots that map AIR variables to usage counts.
17+
/// Generated when converting mutable variables to Z3-level constants.
18+
id_snapshots: Snapshots,
19+
/// Externally facing mapping from snapshot IDs to snapshots that map AIR variables
20+
/// to their concrete values.
21+
/// TODO: Upgrade to a semantics-preserving value type, instead of String.
22+
value_snapshots: HashMap<Ident, HashMap<Ident, String>>,
23+
}
24+
25+
impl<'a> Model<'a> {
26+
/// Returns an (unpopulated) AIR model object. Must call [build()] to fully populate.
27+
/// # Arguments
28+
/// * `model` - The model that Z3 returns
29+
/// * `snapshots` - Internal mapping of snapshot IDs to snapshots that map AIR variables to usage counts.
30+
pub fn new(model: z3::Model<'a>, snapshots: Snapshots) -> Model<'a> {
31+
// println!("Creating a new model with {} snapshots", snapshots.len());
32+
Model { z3_model: model, id_snapshots: snapshots, value_snapshots: HashMap::new() }
33+
}
34+
35+
/// Convert a Z3 AST variable `var_stmt` into a String value
36+
/// Uses `var_name` only for error reporting.
37+
fn lookup_z3_var(&self, var_name: &String, var_smt: &Dynamic) -> String {
38+
if let Some(x) = self.z3_model.eval(var_smt) {
39+
if let Some(b) = x.as_bool() {
40+
format!("{}", b)
41+
} else if let Some(i) = x.as_int() {
42+
format!("{}", i)
43+
} else {
44+
println!("Unexpected type returned from model eval for {}", var_name);
45+
"".to_string()
46+
}
47+
} else {
48+
println!("Failed to extract evaluation of var {} from Z3's model", var_name);
49+
"".to_string()
50+
}
51+
}
52+
53+
/// Populate the AIR-level model based on the Z3 model
54+
/// `local_vars` should be a list of [DeclX::Const] values
55+
/// representing the function's local non-mutable variables
56+
/// (e.g., function parameters)
57+
/// This is decoupled from the Model's constructor so that
58+
/// we only do this expensive work when called in debug mode.
59+
pub fn build(&mut self, context: &mut Context, local_vars: Vec<Decl>) {
60+
println!("Building the AIR model");
61+
for (snap_id, id_snapshot) in &self.id_snapshots {
62+
let mut value_snapshot = HashMap::new();
63+
println!("Snapshot {} has {} variables", snap_id, id_snapshot.len());
64+
for (var_id, var_count) in &*id_snapshot {
65+
let var_name = crate::var_to_const::rename_var(&*var_id, *var_count);
66+
println!("\t{}", var_name);
67+
let var_smt = context
68+
.vars
69+
.get(&var_name)
70+
.unwrap_or_else(|| panic!("internal error: variable {} not found", var_name));
71+
let val = self.lookup_z3_var(&var_name, var_smt);
72+
value_snapshot.insert(var_id.clone(), val);
73+
}
74+
// Add the local variables to every snapshot for uniformity
75+
println!("local_vars has {} variables", local_vars.len());
76+
for decl in local_vars.iter() {
77+
if let DeclX::Const(var_name, typ) = &**decl {
78+
println!("\t{}", var_name);
79+
let var_smt = new_const(context, &var_name, &typ);
80+
let val = self.lookup_z3_var(&var_name, &var_smt);
81+
value_snapshot.insert(var_name.clone(), val);
82+
//value_snapshot.insert(Rc::new((*var_name).clone()), val);
83+
} else {
84+
panic!("Expected local vars to all be constants at this point");
85+
}
86+
}
87+
self.value_snapshots.insert(snap_id.clone(), value_snapshot);
88+
}
89+
}
90+
91+
/// Look up the value of an AIR variable `name` in a given `snapshot`
92+
pub fn query_variable(&self, snapshot: Ident, name: Ident) -> Option<String> {
93+
Some(self.value_snapshots.get(&snapshot)?.get(&name)?.to_string())
94+
}
95+
}
96+
97+
impl<'a> fmt::Display for Model<'a> {
98+
/// Dump the contents of the AIR model for debugging purposes
99+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100+
write!(f, "\nDisplaying model with {} snapshots\n", self.value_snapshots.len())?;
101+
for (snap_id, value_snapshot) in &self.value_snapshots {
102+
write!(f, "Snapshot <{}>:\n", snap_id)?;
103+
for (var_name, value) in &*value_snapshot {
104+
write!(f, "\t{} -> {}\n", var_name, value)?;
105+
}
106+
}
107+
Ok(())
108+
}
109+
}

verify/air/src/smt_util.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use crate::ast::{Typ, TypX};
2+
use crate::context::Context;
3+
use z3::ast::{Bool, Dynamic, Int};
4+
5+
pub(crate) fn new_const<'ctx>(
6+
context: &mut Context<'ctx>,
7+
name: &String,
8+
typ: &Typ,
9+
) -> Dynamic<'ctx> {
10+
match &**typ {
11+
TypX::Bool => Bool::new_const(context.context, name.clone()).into(),
12+
TypX::Int => Int::new_const(context.context, name.clone()).into(),
13+
TypX::Named(x) => {
14+
let sort = &context.typs[x];
15+
let fdecl = z3::FuncDecl::new(context.context, name.clone(), &[], sort);
16+
fdecl.apply(&[])
17+
}
18+
}
19+
}

verify/air/src/smt_verify.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,16 @@
11
use crate::ast::{
2-
BinaryOp, BindX, Constant, Decl, DeclX, Expr, ExprX, Ident, MultiOp, Quant, Query, Span, StmtX,
3-
Typ, TypX, UnaryOp, ValidityResult,
2+
BinaryOp, BindX, Constant, Decl, DeclX, Expr, ExprX, Ident, MultiOp, Quant, Query, Snapshots,
3+
Span, StmtX, Typ, TypX, UnaryOp,
44
};
5-
use crate::context::{AssertionInfo, Context};
5+
use crate::context::{AssertionInfo, Context, ValidityResult};
66
use crate::def::{GLOBAL_PREFIX_LABEL, PREFIX_LABEL};
7+
pub use crate::model::Model;
8+
use crate::smt_util::new_const;
79
use std::collections::{HashMap, HashSet};
810
use std::rc::Rc;
911
use z3::ast::{Ast, Bool, Dynamic, Int};
1012
use z3::{Pattern, SatResult, Sort, Symbol};
1113

12-
fn new_const<'ctx>(context: &mut Context<'ctx>, name: &String, typ: &Typ) -> Dynamic<'ctx> {
13-
match &**typ {
14-
TypX::Bool => Bool::new_const(context.context, name.clone()).into(),
15-
TypX::Int => Int::new_const(context.context, name.clone()).into(),
16-
TypX::Named(x) => {
17-
let sort = &context.typs[x];
18-
let fdecl = z3::FuncDecl::new(context.context, name.clone(), &[], sort);
19-
fdecl.apply(&[])
20-
}
21-
}
22-
}
23-
2414
fn expr_to_smt<'ctx>(context: &mut Context<'ctx>, expr: &Expr) -> Dynamic<'ctx> {
2515
match &**expr {
2616
ExprX::Const(Constant::Bool(b)) => Bool::from_bool(&context.context, *b).into(),
@@ -353,8 +343,10 @@ pub(crate) fn smt_add_decl<'ctx>(context: &mut Context<'ctx>, decl: &Decl) {
353343
fn smt_check_assertion<'ctx>(
354344
context: &mut Context<'ctx>,
355345
infos: &Vec<AssertionInfo>,
346+
snapshots: Snapshots,
347+
local_vars: Vec<Decl>, // Expected to be entirely DeclX::Const
356348
expr: &Expr,
357-
) -> ValidityResult {
349+
) -> ValidityResult<'ctx> {
358350
let mut discovered_span = Rc::new(None);
359351
let mut discovered_global_span = Rc::new(None);
360352
let not_expr = Rc::new(ExprX::Unary(UnaryOp::Not, expr.clone()));
@@ -375,7 +367,7 @@ fn smt_check_assertion<'ctx>(
375367
SatResult::Sat | SatResult::Unknown => {
376368
context.smt_log.log_word("get-model");
377369
let model = context.solver.get_model();
378-
match model {
370+
let mut air_model = match model {
379371
None => {
380372
panic!("SMT solver did not generate a model");
381373
}
@@ -411,14 +403,26 @@ fn smt_check_assertion<'ctx>(
411403
}
412404
}
413405
}
406+
if context.debug {
407+
println!("Z3 model: {}", model);
408+
}
409+
Model::new(model, snapshots)
414410
}
411+
};
412+
if context.debug {
413+
air_model.build(context, local_vars);
415414
}
416-
ValidityResult::Invalid(discovered_span, discovered_global_span)
415+
ValidityResult::Invalid(air_model, discovered_span, discovered_global_span)
417416
}
418417
}
419418
}
420419

421-
pub(crate) fn smt_check_query<'ctx>(context: &mut Context<'ctx>, query: &Query) -> ValidityResult {
420+
pub(crate) fn smt_check_query<'ctx>(
421+
context: &mut Context<'ctx>,
422+
query: &Query,
423+
snapshots: Snapshots,
424+
local_vars: Vec<Decl>,
425+
) -> ValidityResult<'ctx> {
422426
context.smt_log.log_push();
423427
context.solver.push();
424428
context.push_name_scope();
@@ -451,7 +455,7 @@ pub(crate) fn smt_check_query<'ctx>(context: &mut Context<'ctx>, query: &Query)
451455
}
452456

453457
// check assertion
454-
let result = smt_check_assertion(context, &infos, &labeled_assertion);
458+
let result = smt_check_assertion(context, &infos, snapshots, local_vars, &labeled_assertion);
455459

456460
// clean up
457461
context.pop_name_scope();

verify/air/src/tests.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::ast::{CommandX, ValidityResult};
1+
use crate::ast::CommandX;
2+
use crate::context::ValidityResult;
23
#[allow(unused_imports)]
34
use crate::print_parse::{macro_push_node, nodes_to_commands};
45
#[allow(unused_imports)]
@@ -22,7 +23,7 @@ fn run_nodes_as_test(should_typecheck: bool, should_be_valid: bool, nodes: &[Nod
2223
panic!("type error: {}", s);
2324
}
2425
(_, _, true, ValidityResult::Valid) => {}
25-
(_, _, false, ValidityResult::Invalid(_, _)) => {}
26+
(_, _, false, ValidityResult::Invalid(_, _, _)) => {}
2627
(CommandX::CheckValid(_), _, _, _) => {
2728
panic!("unexpected result");
2829
}

0 commit comments

Comments
 (0)