Extended static checking #1987
Replies: 3 comments 1 reply
-
|
My 2 cents: I like that this uses I would also suggest adding a the Also, I would suggest allowing Finally, there is a 3rd point that I think is super helpful on real projects that Rust doesn't natively support but I've found great in practice: something like We don't need all of these in the first version of the implementation, but I'm just mentioning them because I like the idea of extended static checking, and giving people the way to actually support a controlled migration is a very useful idea. Things like |
Beta Was this translation helpful? Give feedback.
-
|
I think this generally sounds great, and I (like Jay) agree with integrating it in a lint-like style. I do think it will require some solid documentation to accompany it. For example, at first glance, I couldn't tell the difference between
Yes, I think this would be a very good idea. Sticking with a consistent style is important as we add lots more configuration options. I also like Jay's suggestion of supporting |
Beta Was this translation helpful? Give feedback.
-
|
Summary of my perspective from the meeting last night I think we can implement many of these in a principled way which retain concrete verification guarantees. Some of these can be viewed as relaxing certain restrictions that aren't required for Verus's proof system to remain sound. For example, panics. Currently, fn add(x: u8, y: u8) -> (z: u8)
requires x + y <= 25
ensures z == x + y;But it could also be specified like this: fn add(x: u8, y: u8) -> (z: u8)
no_unwind when x + y <= 25
ensures z == x.wrapping_add(y)Likewise, fn index<T>(v: &Vec<T>, i: usize) -> (t: &T)
requires 0 <= i < v.len()
ensures t == v@[i]But it could be specified like this: fn index<T>(v: &Vec<T>, i: usize) -> (t: &T)
no_unwind when 0 <= i < v.len()
ensures 0 <= i < v.len() && t == v@[i]So I propose that we implement these attributes by essentially switching the specifications of these common functions to the ones that allow (and specify) the panicking behavior. We can have a special attribute to tell Verus when the unwinding-variant of a function is sound. e.g.: #[verifier::unwinds_when_requires_not_met]
fn index<T>(v: &Vec<T>, i: usize) -> (t: &T)
requires 0 <= i < v.len()
ensures t == v@[i]With an attribute like (Even for user crates, we could, in some cases, automatically apply the Guide level explanation I think we can explain the guarantees roughly like this:
Hopefully this answers Byran's question about the difference between fn test(x: u8, y: u8) {
let z = x + y;
assert(z == x + y);
}Then this would:
NamingAs I said, I think for attributes like 'allow(arith_overflow)' or 'allow(stdlib_panics)' or what have you, we don't need to treat these as soundness-violating at all. We obviously need some kind of scary-sounding names for the soundness violating ones. "assume" is an obvious contendor, though I don't know if it actually makes all of the proposed attributes. Like, for |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
At previous meetings, we've discussed having an extended static checking mode for Verus (by "extended static checking", I mean using formal verification techniques to check some properties of code, but not guaranteeing 100% soundness). The goal of this mode would be to make it easier to apply lightweight checking of some properties to existing Rust code without having to check all the properties that Verus currently checks. For example, users might want to check a few of their own hand-written preconditions, but not check arithmetic overflows or out-of-bounds array accesses. Such users might view errors about potential arithmetic overflows or potential out-of-bounds array accesses as spurious (false positives). Even if these users eventually decide that they want to prove the absence of arithmetic overflows and out-of-bounds array accesses, they might not want to worry about these properties immediately when starting to use Verus.
Here are some examples of checks that Verus performs that could be disabled or relaxed in an extended static checking mode:
requires R, ensures Ecould be treated asrequires true, ensures R ==> Eto eliminate the check ofR.derivemacros (for example,derive(Serialize)for serde)dbg!orprintln!)I would guess that there are more such checks that could be added to this list.
I propose adding individual attributes to control each of these various checks, plus a few aggregate attributes to combine the individual attributes together in a convenient way. In order to control the individual attributes, I propose using something like Rust lint levels (
allow,warn,deny, etc.), with each check being configured by one or more of the following levels:#[verifier::assume(...)#[verifier::assume_but_warn(...)#[verifier::allow(...)#[verifier::warn(...)#[verifier::warn_on_error(...), which would behave likerecommends, warning only when there's a verification error in the function#[verifier::deny(...)Here,
assumeis used for anything that could introduce unsoundness, whileallowis used when soundness is maintained. Each check might only support a subset of the levels listed above. Here are some potential examples:verifier::assume(externals_available_without_declaration)verifier::assume_but_warn(externals_available_without_declaration)verifier::deny(externals_available_without_declaration)verifier::assume(externals_requires_as_ensures[vstd])verifier::assume(ignore_unsupported_features)verifier::assume(no_arith_overflow)verifier::allow(arith_overflow)verifier::warn(arith_overflow)verifier::warn_on_error(arith_overflow)verifier::deny(arith_overflow)We could introduce aggregate configuration options as well. For example,
verifier::assume(configure_for_minimal_checking)might expand to:verifier::assume(externals_available_without_declaration)verifier::assume(externals_requires_as_ensures[vstd])verifier::assume(ignore_unsupported_features)verifier::warn_on_error(arith_overflow)verifier::external_deriveverifier::exec_allows_no_decreases_clauseAs with existing configuration options like
exec_allows_no_decreases_clauseandloop_isolation, these could be configured for a whole crate, a whole module, a function, or perhaps individual expressions, and inner configurations (like on a function) would override outer configurations (like on a whole crate).If people like this overall scheme with
assume,allow,warn, etc., we could also consider renamingexternal_deriveandexec_allows_no_decreases_clauseto use this (for example,verifier::allow(exec_no_decreases_clause)).The existing
--no-cheatingflag would check for uses of the options above. I don't know exactly what the policy should be, but certainly anything that assumes something potentially unsound should be prohibited by--no-cheating.Beta Was this translation helpful? Give feedback.
All reactions