Skip to content

Commit c834d82

Browse files
committed
[compiler-v2] Making v2 the basis of the prover (step #1)
This adds the missing parts to let compiler v2 fully support the specification language, and switches the prover to use v2 as the basis for verification of v1 bytecode. There is one further step needed to run the prover also on the code generated by v2 but that one is smaller than here. Notice that with this, we are dogfooding the v2 compiler frontend in production with the Move prover. There is no switching back and forth, code for the v1 prover integration has been removed. In more detail this does the following: - There are two new env processors, the spec_checker and the spec_rewriter: - `spec_checker` checks the correct use of Move functions in the specification language. Those functions must be 'pure' and not depend on state or use certain other constructs. The checker is to be run as part of the regular compiler chain. - `spec_rewriter` rewrites specification expressions by converting used Move functions into specification functions, and doing other transformations to lift a Move expression into the specification language. This is only run by the prover itself. - Inlining has been extended to deal with specification constructs. - To support the inlining refactoring and the new processors, a new module `rewrite_target` is introduced which allows to collect functions and specification elements in a program in a unified fashion, rewriting them, and writing back to the environment. This new data structure has been inspired by the current design of the inliner and naturally extends it. - A lot of ugliness has been ripped out of the model builder infrastructure (e.g. `TryImplAsSpec` mode is gone, as this is now handled by the `spec_rewriter`). More should come in step #2. - Multiple test cases have been added. - The prover driver has been adapted to use the new components.
1 parent 0c7a0ae commit c834d82

File tree

230 files changed

+2407
-2562
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

230 files changed

+2407
-2562
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

third_party/move/move-compiler-v2/src/env_pipeline/mod.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
77
// TODO: move all other `&mut GlobalEnv` processors into this module.
88

9-
use log::debug;
9+
use log::trace;
1010
use move_model::model::GlobalEnv;
1111
use std::io::Write;
1212

1313
pub mod lambda_lifter;
14+
pub mod rewrite_target;
15+
pub mod spec_checker;
16+
pub mod spec_rewriter;
1417

1518
/// Represents a pipeline of processors working on the global environment.
1619
#[derive(Default)]
@@ -32,10 +35,10 @@ impl<'a> EnvProcessorPipeline<'a> {
3235
/// Runs the pipeline. Running will be ended if any of the steps produces an error.
3336
/// The function returns true if all steps succeeded without errors.
3437
pub fn run(&self, env: &mut GlobalEnv) -> bool {
35-
debug!("before env processor pipeline: {}", env.dump_env());
38+
trace!("before env processor pipeline: {}", env.dump_env());
3639
for (name, proc) in &self.processors {
3740
proc(env);
38-
debug!("after env processor {}", name);
41+
trace!("after env processor {}", name);
3942
if env.has_errors() {
4043
return false;
4144
}
@@ -47,13 +50,13 @@ impl<'a> EnvProcessorPipeline<'a> {
4750
/// only.
4851
pub fn run_and_record(&self, env: &mut GlobalEnv, w: &mut impl Write) -> anyhow::Result<bool> {
4952
let msg = format!("before env processor pipeline:\n{}\n", env.dump_env());
50-
debug!("{}", msg);
53+
trace!("{}", msg);
5154
writeln!(w, "// -- Model dump {}", msg)?;
5255
for (name, proc) in &self.processors {
5356
proc(env);
5457
if !env.has_errors() {
5558
let msg = format!("after env processor {}:\n{}\n", name, env.dump_env());
56-
debug!("{}", msg);
59+
trace!("{}", msg);
5760
writeln!(w, "// -- Model dump {}", msg)?;
5861
} else {
5962
return Ok(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Copyright © Aptos Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use move_model::{
5+
ast::{Exp, Spec, SpecBlockTarget},
6+
model::{FunId, GlobalEnv, NodeId, QualifiedId, SpecFunId},
7+
};
8+
use std::{
9+
collections::{BTreeMap, BTreeSet},
10+
mem,
11+
};
12+
13+
/// Represents a target for rewriting.
14+
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone)]
15+
pub enum RewriteTarget {
16+
MoveFun(QualifiedId<FunId>),
17+
SpecFun(QualifiedId<SpecFunId>),
18+
SpecBlock(SpecBlockTarget),
19+
}
20+
21+
/// Represents the state of a rewriting target.
22+
#[derive(Debug, Clone, Eq, PartialEq)]
23+
pub enum RewriteState {
24+
Unchanged,
25+
Def(Exp),
26+
Spec(Spec),
27+
Abstract,
28+
}
29+
30+
/// Scope for collecting targets.
31+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
32+
pub enum RewritingScope {
33+
CompilationTarget,
34+
Everything,
35+
}
36+
37+
/// Represents a set of rewriting targets in the given state.
38+
#[derive(Clone)]
39+
pub struct RewriteTargets {
40+
pub targets: BTreeMap<RewriteTarget, RewriteState>,
41+
}
42+
43+
impl RewriteTargets {
44+
/// Create a new set of rewrite targets, collecting them as specified by `scope`.
45+
/// Those targets are initially associated with `Unchanged` state.
46+
pub fn create(env: &GlobalEnv, scope: RewritingScope) -> Self {
47+
let mut targets = vec![];
48+
let add_spec =
49+
|targets: &mut Vec<RewriteTarget>, sb_target: SpecBlockTarget, spec: &Spec| {
50+
if !spec.is_empty() {
51+
targets.push(RewriteTarget::SpecBlock(sb_target))
52+
}
53+
};
54+
for module in env.get_modules() {
55+
if scope == RewritingScope::Everything || module.is_target() {
56+
for func in module.get_functions() {
57+
let id = func.get_qualified_id();
58+
targets.push(RewriteTarget::MoveFun(id));
59+
add_spec(
60+
&mut targets,
61+
SpecBlockTarget::Function(id.module_id, id.id),
62+
&func.get_spec(),
63+
);
64+
}
65+
for (spec_fun_id, _) in module.get_spec_funs() {
66+
targets.push(RewriteTarget::SpecFun(
67+
module.get_id().qualified(*spec_fun_id),
68+
));
69+
}
70+
for struct_env in module.get_structs() {
71+
add_spec(
72+
&mut targets,
73+
SpecBlockTarget::Struct(module.get_id(), struct_env.get_id()),
74+
&struct_env.get_spec(),
75+
)
76+
}
77+
if !module.get_spec().is_empty() {
78+
add_spec(
79+
&mut targets,
80+
SpecBlockTarget::Module(module.get_id()),
81+
&module.get_spec(),
82+
);
83+
}
84+
}
85+
}
86+
Self {
87+
targets: targets
88+
.into_iter()
89+
.map(|target| (target, RewriteState::Unchanged))
90+
.collect(),
91+
}
92+
}
93+
94+
/// Filters the targets according to the predicate.
95+
pub fn filter(&mut self, pred: impl Fn(&RewriteTarget, &RewriteState) -> bool) {
96+
self.targets = mem::take(&mut self.targets)
97+
.into_iter()
98+
.filter(|(t, s)| pred(t, s))
99+
.collect();
100+
}
101+
102+
/// Iterates all targets.
103+
pub fn iter(&self) -> impl Iterator<Item = (&RewriteTarget, &RewriteState)> + '_ {
104+
self.targets.iter()
105+
}
106+
107+
/// Returns an iteration of the target keys.
108+
pub fn keys(&self) -> impl Iterator<Item = RewriteTarget> + '_ {
109+
self.targets.keys().cloned()
110+
}
111+
112+
/// Adds a new rewrite target in state `Unchanged` if it doesn't exist yet. Returns
113+
/// a boolean whether the entry is new and a mutable reference to the state.
114+
pub fn entry(&mut self, target: RewriteTarget) -> (bool, &mut RewriteState) {
115+
let mut is_new = false;
116+
let state = self.targets.entry(target).or_insert_with(|| {
117+
is_new = true;
118+
RewriteState::Unchanged
119+
});
120+
(is_new, state)
121+
}
122+
123+
/// Gets the current state of the target.
124+
pub fn state(&self, target: &RewriteTarget) -> &RewriteState {
125+
self.targets.get(target).expect("state defined")
126+
}
127+
128+
/// Gets the mutable current state of the target.
129+
pub fn state_mut(&mut self, target: &RewriteTarget) -> &mut RewriteState {
130+
self.targets.get_mut(target).expect("state defined")
131+
}
132+
133+
/// Updates the global env based on the current state. This consumes
134+
/// the rewrite targets.
135+
pub fn write_to_env(self, env: &mut GlobalEnv) {
136+
for (target, state) in self.targets {
137+
use RewriteState::*;
138+
use RewriteTarget::*;
139+
match (target, state) {
140+
(_, Unchanged) => {},
141+
(MoveFun(fnid), Def(def)) => env.set_function_def(fnid, def),
142+
(SpecFun(fnid), Def(def)) => env.get_spec_fun_mut(fnid).body = Some(def),
143+
(SpecBlock(sb_target), Spec(spec)) => {
144+
*env.get_spec_block_mut(&sb_target) = spec;
145+
},
146+
_ => panic!("unexpected rewrite target and result combination"),
147+
}
148+
}
149+
}
150+
}
151+
152+
impl RewriteTarget {
153+
/// Gets the call sites for the target.
154+
pub fn called_funs_with_call_sites(
155+
&self,
156+
env: &GlobalEnv,
157+
) -> BTreeMap<QualifiedId<FunId>, BTreeSet<NodeId>> {
158+
use RewriteTarget::*;
159+
match self {
160+
MoveFun(id) => env
161+
.get_function(*id)
162+
.get_def()
163+
.map(|e| e.called_funs_with_callsites())
164+
.unwrap_or_default(),
165+
SpecFun(id) => env
166+
.get_spec_fun(*id)
167+
.body
168+
.as_ref()
169+
.map(|e| e.called_funs_with_callsites())
170+
.unwrap_or_default(),
171+
SpecBlock(target) => {
172+
let spec = env.get_spec_block(target);
173+
spec.called_funs_with_callsites()
174+
},
175+
}
176+
}
177+
178+
/// Get the environment state of this target in form of a RewriteState.
179+
pub fn get_env_state(&self, env: &GlobalEnv) -> RewriteState {
180+
use RewriteState::*;
181+
use RewriteTarget::*;
182+
match self {
183+
MoveFun(fid) => env
184+
.get_function(*fid)
185+
.get_def()
186+
.map(|e| Def(e.clone()))
187+
.unwrap_or(Abstract),
188+
SpecFun(fid) => env
189+
.get_spec_fun(*fid)
190+
.body
191+
.clone()
192+
.map(Def)
193+
.unwrap_or(Abstract),
194+
SpecBlock(sb_target) => Spec(env.get_spec_block(sb_target).clone()),
195+
}
196+
}
197+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright © Aptos Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! The spec checker runs over the specifications of the target modules:
5+
//!
6+
//! - It checks whether the constructs they use are pure. If a specification
7+
//! expression calls a Move function it checks that for pureness as well.
8+
//! - It checks whether struct invariants do not depend on global state.
9+
10+
use crate::env_pipeline::rewrite_target::{
11+
RewriteState, RewriteTarget, RewriteTargets, RewritingScope,
12+
};
13+
use codespan_reporting::diagnostic::Severity;
14+
use log::info;
15+
use move_model::{
16+
ast::{Exp, Spec},
17+
model::{FunId, GlobalEnv, NodeId, QualifiedId},
18+
pureness_checker::{FunctionPurenessChecker, FunctionPurenessCheckerMode},
19+
};
20+
21+
pub fn run_spec_checker(env: &GlobalEnv) {
22+
info!("checking specifications");
23+
24+
// Targets are all spec functions and spec blocks, as well as functions to
25+
// process inline specs.
26+
let mut targets = RewriteTargets::create(env, RewritingScope::CompilationTarget);
27+
targets.filter(|target, _| {
28+
matches!(
29+
target,
30+
RewriteTarget::SpecFun(_) | RewriteTarget::SpecBlock(_) | RewriteTarget::MoveFun(_)
31+
)
32+
});
33+
34+
// Walk over those targets and check them for pureness.
35+
for target in targets.keys() {
36+
match (target.clone(), target.get_env_state(env)) {
37+
(RewriteTarget::MoveFun(_), RewriteState::Def(exp)) => {
38+
exp.visit_inline_specs(&mut |s| {
39+
check_spec(env, s);
40+
true
41+
})
42+
},
43+
(RewriteTarget::SpecFun(_), RewriteState::Def(exp)) => check_exp(env, &exp),
44+
(RewriteTarget::SpecBlock(_), RewriteState::Spec(spec)) => check_spec(env, &spec),
45+
_ => {},
46+
}
47+
}
48+
}
49+
50+
fn check_exp(env: &GlobalEnv, exp: &Exp) {
51+
let mut error_reported = false;
52+
let mut checker = FunctionPurenessChecker::new(
53+
FunctionPurenessCheckerMode::Specification,
54+
|node_id, msg, call_chain| report_error(env, &mut error_reported, node_id, msg, call_chain),
55+
);
56+
checker.check_exp(env, exp);
57+
}
58+
59+
fn check_spec(env: &GlobalEnv, spec: &Spec) {
60+
let mut error_reported = false;
61+
let mut checker = FunctionPurenessChecker::new(
62+
FunctionPurenessCheckerMode::Specification,
63+
|node_id, msg, call_chain| report_error(env, &mut error_reported, node_id, msg, call_chain),
64+
);
65+
checker.check_spec(env, spec);
66+
}
67+
68+
fn report_error(
69+
env: &GlobalEnv,
70+
error_reported: &mut bool,
71+
id: NodeId,
72+
msg: &str,
73+
call_chain: &[(QualifiedId<FunId>, NodeId)],
74+
) {
75+
// We report the first error only because otherwise the error messages can be
76+
// overwhelming, if the user e.g. accidentally calls a complex system function.
77+
if *error_reported {
78+
return;
79+
}
80+
// The first call in call_chain is the one from the specification function to
81+
// a Move function. We take this as the primary anchor for the error message
82+
let print_fun = |f: QualifiedId<FunId>| env.get_function(f).get_name_str();
83+
if call_chain.is_empty() {
84+
// Direct report
85+
env.diag_with_primary_and_labels(
86+
Severity::Error,
87+
&env.get_node_loc(id),
88+
"specification expression cannot use impure construct",
89+
msg,
90+
vec![],
91+
);
92+
} else {
93+
let (first_fun, first_id) = call_chain[0];
94+
let mut call_chain_info = vec![];
95+
// First print the sequence of calls leading us to the issue
96+
for i in 1..call_chain.len() {
97+
let previous_fun = print_fun(call_chain[i - 1].0);
98+
let this_fun = print_fun(call_chain[1].0);
99+
let this_loc = env.get_node_loc(call_chain[1].1);
100+
call_chain_info.push((
101+
this_loc,
102+
format!(
103+
"transitively calling `{}` from `{}` here",
104+
this_fun, previous_fun
105+
),
106+
))
107+
}
108+
// Next print the particular issue detected
109+
let last_fun = call_chain.last().unwrap().0;
110+
call_chain_info.push((
111+
env.get_node_loc(id),
112+
format!("in `{}`: {}", print_fun(last_fun), msg),
113+
));
114+
115+
env.diag_with_primary_and_labels(
116+
Severity::Error,
117+
&env.get_node_loc(first_id),
118+
&format!(
119+
"specification expression cannot call impure \
120+
Move function `{}`",
121+
env.get_function(first_fun).get_name_str()
122+
),
123+
"called here",
124+
call_chain_info,
125+
);
126+
}
127+
*error_reported = true;
128+
}

0 commit comments

Comments
 (0)