Skip to content

Commit c196348

Browse files
authored
Enhanced Python Constraint Support (#27)
* Python bindings now use a single-threaded version of synthesis. where ALL work takes place in the main thread and all z3 usage uses the main python z3 context. This is used to enable... * Constraints on the system preconditions, postconditions and transitions can be expressed as python functions or lambdas. These can be given to the synthesis procedure, which will adapt them into the rust-side's closure-based constraint system. This makes it very easy to specify non-trivial arbitrary constraints from python. * General refactors around model building to make it easier to reuse
1 parent 25c8380 commit c196348

File tree

13 files changed

+360
-56
lines changed

13 files changed

+360
-56
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ jobs:
3131
- uses: dtolnay/rust-toolchain@master
3232
with:
3333
toolchain: ${{matrix.rust}}
34-
- run: cargo build --all-features
34+
- run: cargo build --all-features --manifest-path crackers/Cargo.toml

crackers/src/bin/crackers/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ fn synthesize(config: PathBuf) -> anyhow::Result<()> {
129129
match result {
130130
Ok(res) => match res {
131131
DecisionResult::AssignmentFound(a) => {
132+
let z3 = Context::new(&Config::new());
133+
let a = a.build(&z3)?;
132134
event!(Level::INFO, "Synthesis successful :)");
133135
event!(Level::INFO, "{}", a)
134136
}

crackers/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ pub enum CrackersError {
3737
LibraryConfig(#[from] GadgetLibraryConfigBuilderError),
3838
#[error("Invalid synthesis params")]
3939
SynthesisParams(#[from] SynthesisParamsBuilderError),
40+
#[cfg(feature = "pyo3")]
41+
#[error("Python error: {0}")]
42+
PythonError(#[from] PyErr),
4043
}
4144

4245
#[cfg(feature = "pyo3")]
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use crate::error::CrackersError;
2+
use crate::gadget::Gadget;
3+
use crate::gadget::library::GadgetLibrary;
4+
use crate::synthesis::assignment_model::AssignmentModel;
5+
use crate::synthesis::builder::{StateConstraintGenerator, TransitionConstraintGenerator};
6+
use crate::synthesis::pcode_theory::pcode_assignment::PcodeAssignment;
7+
use jingle::JingleContext;
8+
use jingle::modeling::{ModeledBlock, ModeledInstruction};
9+
use jingle::sleigh::{ArchInfoProvider, Instruction, SpaceInfo, VarNode};
10+
use std::fmt::{Debug, Formatter};
11+
use std::sync::Arc;
12+
use z3::{Context, Solver};
13+
14+
// todo: this should probably just be a struct in Jingle and we can stop with this trait nonsense
15+
#[derive(Clone, Debug)]
16+
pub struct ArchInfo {
17+
spaces: Vec<SpaceInfo>,
18+
default_code_space_index: usize,
19+
registers: Vec<(VarNode, String)>,
20+
}
21+
22+
impl From<&GadgetLibrary> for ArchInfo {
23+
fn from(value: &GadgetLibrary) -> Self {
24+
Self {
25+
default_code_space_index: value.get_code_space_idx(),
26+
registers: value
27+
.get_registers()
28+
.map(|(a, b)| (a.clone(), b.to_string()))
29+
.collect(),
30+
spaces: value.get_all_space_info().cloned().collect(),
31+
}
32+
}
33+
}
34+
35+
impl ArchInfoProvider for ArchInfo {
36+
fn get_space_info(&self, idx: usize) -> Option<&SpaceInfo> {
37+
self.spaces.get(idx)
38+
}
39+
40+
fn get_all_space_info(&self) -> impl Iterator<Item = &SpaceInfo> {
41+
self.spaces.iter()
42+
}
43+
44+
fn get_code_space_idx(&self) -> usize {
45+
self.default_code_space_index
46+
}
47+
48+
fn get_register(&self, name: &str) -> Option<&VarNode> {
49+
self.registers.iter().find(|f| f.1 == name).map(|f| &f.0)
50+
}
51+
52+
fn get_register_name(&self, location: &VarNode) -> Option<&str> {
53+
self.registers
54+
.iter()
55+
.find(|f| f.0 == *location)
56+
.map(|f| f.1.as_str())
57+
}
58+
59+
fn get_registers(&self) -> impl Iterator<Item = (&VarNode, &str)> {
60+
self.registers.iter().map(|(f, v)| (f, v.as_str()))
61+
}
62+
}
63+
64+
#[derive(Clone)]
65+
pub struct AssignmentModelBuilder {
66+
pub templates: Vec<Instruction>,
67+
pub gadgets: Vec<Gadget>,
68+
pub preconditions: Vec<Arc<StateConstraintGenerator>>,
69+
pub postconditions: Vec<Arc<StateConstraintGenerator>>,
70+
pub pointer_invariants: Vec<Arc<TransitionConstraintGenerator>>,
71+
pub arch_info: ArchInfo,
72+
}
73+
74+
impl Debug for AssignmentModelBuilder {
75+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
76+
f.debug_struct("AssignmentModelBuilder")
77+
.field("templates", &self.templates)
78+
.field("gadgets", &self.gadgets)
79+
.field("arch_info", &self.arch_info)
80+
.finish()
81+
}
82+
}
83+
84+
impl AssignmentModelBuilder {
85+
fn make_pcode_model<'ctx>(
86+
&self,
87+
jingle: &JingleContext<'ctx>,
88+
) -> Result<PcodeAssignment<'ctx>, CrackersError> {
89+
let modeled_spec: Result<Vec<ModeledInstruction<'ctx>>, _> = self
90+
.templates
91+
.iter()
92+
.cloned()
93+
.map(|i| ModeledInstruction::new(i, jingle).map_err(CrackersError::from))
94+
.collect();
95+
let modeled_spec = modeled_spec?;
96+
let modeled_gadgets: Result<_, _> = self
97+
.gadgets
98+
.iter()
99+
.cloned()
100+
.map(|i| i.model(jingle))
101+
.collect();
102+
let modeled_gadgets = modeled_gadgets?;
103+
Ok(PcodeAssignment::new(
104+
modeled_spec,
105+
modeled_gadgets,
106+
self.preconditions.clone(),
107+
self.postconditions.clone(),
108+
self.pointer_invariants.clone(),
109+
))
110+
}
111+
pub fn build<'ctx>(
112+
&self,
113+
z3: &'ctx Context,
114+
) -> Result<AssignmentModel<'ctx, ModeledBlock<'ctx>>, CrackersError> {
115+
let jingle = JingleContext::new(z3, &self.arch_info);
116+
117+
let pcode_model = self.make_pcode_model(&jingle)?;
118+
let s = Solver::new(jingle.z3);
119+
pcode_model.check(&jingle, &s)
120+
}
121+
}

crackers/src/synthesis/assignment_model.rs renamed to crackers/src/synthesis/assignment_model/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
pub mod builder;
2+
13
use std::fmt::{Display, Formatter};
24

35
use jingle::modeling::{ModelingContext, State};
@@ -13,7 +15,7 @@ pub struct AssignmentModel<'ctx, T: ModelingContext<'ctx>> {
1315
}
1416

1517
impl<'ctx, T: ModelingContext<'ctx>> AssignmentModel<'ctx, T> {
16-
pub fn generate(model: Model<'ctx>, gadgets: Vec<T>) -> Self {
18+
pub fn new(model: Model<'ctx>, gadgets: Vec<T>) -> Self {
1719
Self { model, gadgets }
1820
}
1921

crackers/src/synthesis/combined.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use jingle::modeling::ModeledBlock;
21
use jingle::sleigh::Instruction;
32
use tracing::{Level, event};
43
use z3::Context;
@@ -14,7 +13,7 @@ pub struct CombinedAssignmentSynthesis<'a> {
1413
}
1514

1615
impl<'a> CombinedAssignmentSynthesis<'a> {
17-
pub fn decide(&mut self) -> Result<DecisionResult<'a, ModeledBlock<'a>>, CrackersError> {
16+
pub fn decide(&mut self) -> Result<DecisionResult, CrackersError> {
1817
let mut ordering: Vec<Vec<Instruction>> = self
1918
.base_config
2019
.instructions
@@ -29,7 +28,7 @@ impl<'a> CombinedAssignmentSynthesis<'a> {
2928
// todo: gross hack to avoid rewriting the partitioning algorithm to be breadth-first
3029
ordering.sort_by(|a, b| a.len().partial_cmp(&b.len()).unwrap());
3130
let iter = ordering.into_iter();
32-
let mut last: Option<DecisionResult<'a, ModeledBlock<'a>>> = None;
31+
let mut last: Option<_> = None;
3332
for instructions in iter {
3433
// todo: filter for instruction combinations that have already been ruled out?
3534
// if instructions.iter().any(|i| blacklist.contains(i)) {
@@ -66,6 +65,58 @@ impl<'a> CombinedAssignmentSynthesis<'a> {
6665
last.ok_or(CrackersError::EmptySpecification)
6766
}
6867

68+
// gross but I don't feel like rewriting this right now
69+
pub fn decide_single_threaded(&mut self) -> Result<DecisionResult, CrackersError> {
70+
let mut ordering: Vec<Vec<Instruction>> = self
71+
.base_config
72+
.instructions
73+
.partitions()
74+
.map(|part| {
75+
part.into_iter()
76+
.map(|instrs| Instruction::try_from(instrs).unwrap())
77+
.collect::<Vec<Instruction>>()
78+
})
79+
.collect();
80+
// let mut blacklist = HashSet::new();
81+
// todo: gross hack to avoid rewriting the partitioning algorithm to be breadth-first
82+
ordering.sort_by(|a, b| a.len().partial_cmp(&b.len()).unwrap());
83+
let iter = ordering.into_iter();
84+
let mut last: Option<_> = None;
85+
for instructions in iter {
86+
// todo: filter for instruction combinations that have already been ruled out?
87+
// if instructions.iter().any(|i| blacklist.contains(i)) {
88+
// continue;
89+
// }
90+
let mut new_config = self.base_config.clone();
91+
new_config.instructions = instructions;
92+
let synth = AssignmentSynthesis::new(self.z3, &new_config);
93+
if let Ok(mut synth) = synth {
94+
// this one constructed, let's try it
95+
match synth.decide_single_threaded() {
96+
Ok(result) => {
97+
match result {
98+
DecisionResult::AssignmentFound(a) => {
99+
return Ok(DecisionResult::AssignmentFound(a));
100+
}
101+
DecisionResult::Unsat(e) => {
102+
// todo: add in bad combos here
103+
event!(Level::WARN, "{:?}", e);
104+
// e.indexes.iter().for_each(|i|{blacklist.insert(new_config.instructions[*i].clone());});
105+
last = Some(DecisionResult::Unsat(e))
106+
}
107+
}
108+
}
109+
Err(e) => {
110+
event!(Level::ERROR, "{:?}", e)
111+
}
112+
}
113+
} else {
114+
event!(Level::WARN, "Failed to find gadgets for partition")
115+
}
116+
}
117+
// Only an empty specification can possibly result in this being `None`
118+
last.ok_or(CrackersError::EmptySpecification)
119+
}
69120
pub fn new(z3: &'a Context, base_config: SynthesisParams) -> Self {
70121
Self { z3, base_config }
71122
}

crackers/src/synthesis/mod.rs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
use jingle::JingleContext;
2-
use jingle::modeling::{ModeledBlock, ModeledInstruction, ModelingContext};
2+
use jingle::modeling::ModeledInstruction;
33
use jingle::sleigh::Instruction;
44
use std::cmp::Ordering;
55
use std::sync::Arc;
66
use tracing::{Level, event, instrument};
7-
use z3::{Config, Context, Solver};
7+
use z3::{Config, Context};
88

99
use crate::error::CrackersError;
1010
use crate::error::CrackersError::EmptySpecification;
1111
use crate::gadget::candidates::{CandidateBuilder, Candidates};
1212
use crate::gadget::library::GadgetLibrary;
13-
use crate::synthesis::assignment_model::AssignmentModel;
13+
use crate::synthesis::assignment_model::builder::{ArchInfo, AssignmentModelBuilder};
1414
use crate::synthesis::builder::{
1515
StateConstraintGenerator, SynthesisParams, SynthesisSelectionStrategy,
1616
TransitionConstraintGenerator,
1717
};
1818
use crate::synthesis::pcode_theory::builder::PcodeTheoryBuilder;
19-
use crate::synthesis::pcode_theory::pcode_assignment::PcodeAssignment;
2019
use crate::synthesis::pcode_theory::theory_worker::TheoryWorker;
2120
use crate::synthesis::selection_strategy::AssignmentResult::{Failure, Success};
2221
use crate::synthesis::selection_strategy::OuterProblem::{OptimizeProb, SatProb};
2322
use crate::synthesis::selection_strategy::optimization_problem::OptimizationProblem;
2423
use crate::synthesis::selection_strategy::sat_problem::SatProblem;
2524
use crate::synthesis::selection_strategy::{OuterProblem, SelectionFailure, SelectionStrategy};
25+
use crate::synthesis::slot_assignments::SlotAssignments;
2626

2727
pub mod assignment_model;
2828
pub mod builder;
@@ -45,8 +45,8 @@ impl PartialOrd for Decision {
4545
}
4646

4747
#[derive(Debug)]
48-
pub enum DecisionResult<'ctx, T: ModelingContext<'ctx>> {
49-
AssignmentFound(AssignmentModel<'ctx, T>),
48+
pub enum DecisionResult {
49+
AssignmentFound(AssignmentModelBuilder),
5050
Unsat(SelectionFailure),
5151
}
5252

@@ -107,11 +107,54 @@ impl<'ctx> AssignmentSynthesis<'ctx> {
107107
})
108108
}
109109

110+
fn make_model_builder(&self, slot_assignments: SlotAssignments) -> AssignmentModelBuilder {
111+
AssignmentModelBuilder {
112+
templates: self.instructions.clone(),
113+
gadgets: slot_assignments.interpret_from_library(&self.candidates),
114+
preconditions: self.preconditions.clone(),
115+
postconditions: self.postconditions.clone(),
116+
pointer_invariants: self.pointer_invariants.clone(),
117+
arch_info: ArchInfo::from(self.library.as_ref()),
118+
}
119+
}
120+
121+
fn make_pcode_theory_builder(&self) -> PcodeTheoryBuilder {
122+
PcodeTheoryBuilder::new(self.candidates.clone(), &self.library)
123+
.with_pointer_invariants(&self.pointer_invariants)
124+
.with_preconditions(&self.preconditions)
125+
.with_postconditions(&self.postconditions)
126+
.with_max_candidates(self.candidates_per_slot)
127+
.with_templates(self.instructions.clone().into_iter())
128+
}
129+
130+
pub fn decide_single_threaded(&mut self) -> Result<DecisionResult, CrackersError> {
131+
let theory_builder = self.make_pcode_theory_builder();
132+
let theory = theory_builder.build(self.z3)?;
133+
loop {
134+
let assignment = self.outer_problem.get_assignments()?;
135+
match assignment {
136+
Success(a) => {
137+
let theory_result = theory.check_assignment(&a)?;
138+
match theory_result {
139+
None => {
140+
// success
141+
return Ok(DecisionResult::AssignmentFound(self.make_model_builder(a)));
142+
}
143+
Some(conflict) => {
144+
self.outer_problem.add_theory_clauses(&conflict);
145+
}
146+
}
147+
}
148+
Failure(d) => return Ok(DecisionResult::Unsat(d)),
149+
}
150+
}
151+
}
110152
#[instrument(skip_all)]
111-
pub fn decide(&mut self) -> Result<DecisionResult<'ctx, ModeledBlock<'ctx>>, CrackersError> {
153+
pub fn decide(&mut self) -> Result<DecisionResult, CrackersError> {
112154
let mut req_channels = vec![];
113155
let mut kill_senders = vec![];
114-
let theory_builder = PcodeTheoryBuilder::new(self.candidates.clone(), &self.library)
156+
let library = self.library.clone();
157+
let theory_builder = PcodeTheoryBuilder::new(self.candidates.clone(), &library)
115158
.with_pointer_invariants(&self.pointer_invariants)
116159
.with_preconditions(&self.preconditions)
117160
.with_postconditions(&self.postconditions)
@@ -188,19 +231,9 @@ impl<'ctx> AssignmentSynthesis<'ctx> {
188231
x.send(()).unwrap();
189232
}
190233
kill_senders.clear();
191-
let t = theory_builder.clone();
192-
let jingle = JingleContext::new(self.z3, self.library.as_ref());
193-
let a: PcodeAssignment<'ctx> =
194-
t.build_assignment(&jingle, response.assignment)?;
195-
event!(
196-
Level::DEBUG,
197-
"Workers Terminated; building and checking model"
198-
);
199-
let solver = Solver::new(self.z3);
200-
let jingle = JingleContext::new(self.z3, self.library.as_ref());
201-
let model = a.check(&jingle, &solver)?;
202-
event!(Level::DEBUG, "Model built");
203-
return Ok(DecisionResult::AssignmentFound(model));
234+
return Ok(DecisionResult::AssignmentFound(
235+
self.make_model_builder(response.assignment),
236+
));
204237
}
205238
Some(c) => {
206239
event!(

crackers/src/synthesis/pcode_theory/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ impl<'ctx, S: ModelingContext<'ctx>> PcodeTheory<'ctx, S> {
7373
let final_state = self.j.fresh_state();
7474
self.solver
7575
.assert(&assert_concat(self.j.z3, &self.templates)?);
76-
7776
let mut assertions: Vec<ConjunctiveConstraint> = Vec::new();
7877
for (index, x) in gadgets.windows(2).enumerate() {
7978
let branch = Bool::fresh_const(self.j.z3, "b");

crackers/src/synthesis/pcode_theory/pcode_assignment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl<'ctx> PcodeAssignment<'ctx> {
6969
let model = solver
7070
.get_model()
7171
.ok_or(CrackersError::ModelGenerationError)?;
72-
Ok(AssignmentModel::generate(model, self.eval_trace.to_vec()))
72+
Ok(AssignmentModel::new(model, self.eval_trace.to_vec()))
7373
}
7474
}
7575
}

0 commit comments

Comments
 (0)