Skip to content

Commit 9a5aade

Browse files
Add verifier::assume(externals_available_without_declaration) (verus-lang#1988)
1 parent c6f2717 commit 9a5aade

23 files changed

Lines changed: 543 additions & 209 deletions

source/rust_verify/src/attributes.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ pub(crate) enum Attr {
345345
OpenVisibilityQualifier,
346346
// Allow the function to not have decreases clauses
347347
ExecAllowNoDecreasesClause,
348+
// Assume that external items can be used without a Verus declaration (unsound)
349+
ExternalsAvailableWithoutDeclaration(bool),
348350
// Assume that the function terminates
349351
AssumeTermination,
350352
// Proxy containing unerased code
@@ -677,6 +679,16 @@ pub(crate) fn parse_attrs(
677679
AttrTree::Fun(_, arg, None) if arg == "exec_allows_no_decreases_clause" => {
678680
v.push(Attr::ExecAllowNoDecreasesClause);
679681
}
682+
AttrTree::Fun(_, arg, Some(box [AttrTree::Fun(_, r, None)]))
683+
if arg == "assume" && r == "externals_available_without_declaration" =>
684+
{
685+
v.push(Attr::ExternalsAvailableWithoutDeclaration(true))
686+
}
687+
AttrTree::Fun(_, arg, Some(box [AttrTree::Fun(_, r, None)]))
688+
if arg == "deny" && r == "externals_available_without_declaration" =>
689+
{
690+
v.push(Attr::ExternalsAvailableWithoutDeclaration(false))
691+
}
680692
AttrTree::Fun(_, arg, None) if arg == "tracked_swap_primitive" => {
681693
v.push(Attr::TrackedSwap)
682694
}
@@ -942,6 +954,18 @@ pub(crate) fn get_allow_exec_allows_no_decreases_clause_walk_parents<'tcx>(
942954
false
943955
}
944956

957+
pub(crate) fn get_externals_available_without_declaration_walk_parents<'tcx>(
958+
tcx: rustc_middle::ty::TyCtxt<'tcx>,
959+
def_id: rustc_span::def_id::DefId,
960+
) -> bool {
961+
for attr in parse_attrs_walk_parents(tcx, def_id) {
962+
if let Attr::ExternalsAvailableWithoutDeclaration(flag) = attr {
963+
return flag;
964+
}
965+
}
966+
false
967+
}
968+
945969
pub(crate) fn get_ghost_block_opt(attrs: &[Attribute]) -> Option<GhostBlockAttr> {
946970
for attr in parse_attrs_opt(attrs, None) {
947971
match attr {
@@ -1126,6 +1150,7 @@ pub(crate) struct VerifierAttrs {
11261150
pub(crate) open_visibility_qualifier: bool,
11271151
pub(crate) assume_termination: bool,
11281152
pub(crate) exec_allows_no_decreases_clause: bool,
1153+
pub(crate) externals_available_without_declaration: Option<bool>,
11291154
pub(crate) unerased_proxy: bool,
11301155
pub(crate) encoded_const: bool,
11311156
pub(crate) encoded_static: bool,
@@ -1300,6 +1325,7 @@ pub(crate) fn get_verifier_attrs_maybe_check(
13001325
open_visibility_qualifier: false,
13011326
assume_termination: false,
13021327
exec_allows_no_decreases_clause: false,
1328+
externals_available_without_declaration: None,
13031329
unerased_proxy: false,
13041330
encoded_const: false,
13051331
encoded_static: false,
@@ -1380,6 +1406,9 @@ pub(crate) fn get_verifier_attrs_maybe_check(
13801406
Attr::OpenVisibilityQualifier => vs.open_visibility_qualifier = true,
13811407
Attr::AssumeTermination => vs.assume_termination = true,
13821408
Attr::ExecAllowNoDecreasesClause => vs.exec_allows_no_decreases_clause = true,
1409+
Attr::ExternalsAvailableWithoutDeclaration(flag) => {
1410+
vs.externals_available_without_declaration = Some(flag)
1411+
}
13831412
Attr::UnerasedProxy => vs.unerased_proxy = true,
13841413
Attr::EncodedConst => vs.encoded_const = true,
13851414
Attr::EncodedStatic => vs.encoded_static = true,

source/rust_verify/src/erase.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ pub enum ResolvedCall {
5050
/// The call is to an operator like == or + that should be compiled.
5151
CompilableOperator(CompilableOperator),
5252
/// The call is to a function, and we record the name of the function here
53-
/// (both unresolved and resolved), as well as an in_ghost flag.
53+
/// (both unresolved and resolved), as well as (in_ghost, assume_external) flags.
5454
/// This is replaced by CallModes as soon as the modes are available.
55-
Call(Fun, Fun, bool),
55+
Call(Fun, Fun, bool, bool),
5656
/// Path and variant of datatype constructor
5757
Ctor(Path, vir::ast::Ident),
5858
/// Path and variant of datatype constructor. Used for ExprKind::Struct nodes.
@@ -168,11 +168,15 @@ fn resolved_call_to_call_erase(
168168
Ok(match resolved_call {
169169
ResolvedCall::SpecPure => CallErasure::EraseTree(TreeErase::IncludeBasicChecks),
170170
ResolvedCall::SpecAllowProofArgs => CallErasure::Call(NodeErase::Erase),
171-
ResolvedCall::Call(ufun, rfun, in_ghost) => {
171+
ResolvedCall::Call(ufun, rfun, in_ghost, assume_external) => {
172172
// Note: in principle, the unresolved function ufun should always be present,
173173
// but we currently allow external declarations of resolved trait functions
174174
// without a corresponding external trait declaration.
175175
let Some(f) = functions.get(ufun).or_else(|| functions.get(rfun)) else {
176+
if *assume_external {
177+
let erase = CallErasure::Call(NodeErase::Keep);
178+
return Ok(erase);
179+
}
176180
dbg!(ufun, rfun);
177181
panic!("internal Verus error: could not find mode declarations for function")
178182
};

source/rust_verify/src/fn_call_to_vir.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,23 @@ fn fn_call_or_assoc_const_to_vir<'tcx>(
300300
}
301301
};
302302

303-
record_call(bctx, expr, ResolvedCall::Call(name.clone(), record_name, bctx.in_ghost));
303+
let assume_external_allowed =
304+
crate::attributes::get_externals_available_without_declaration_walk_parents(
305+
bctx.ctxt.tcx,
306+
bctx.fun_id,
307+
);
308+
309+
let resolved_call =
310+
ResolvedCall::Call(name.clone(), record_name, bctx.in_ghost, assume_external_allowed);
311+
record_call(bctx, expr, resolved_call);
304312

305313
let vir_args = if let Some(args) = args { mk_vir_args(bctx, &args)? } else { vec![] };
306314

307315
let typ_args = mk_typ_args(bctx, node_substs, f, expr.span)?;
308316
let impl_paths = get_impl_paths(bctx, f, node_substs, None, const_var, expr.span)?;
309-
let target =
310-
CallTarget::Fun(target_kind, name, typ_args, impl_paths, autospec_usage, const_var);
317+
let call_target_attrs =
318+
vir::ast::CallTargetAttrs { autospec: autospec_usage, assume_external_allowed, const_var };
319+
let target = CallTarget::Fun(target_kind, name, typ_args, impl_paths, call_target_attrs);
311320
Ok(bctx.spanned_typed_new(
312321
expr.span,
313322
&expr_typ()?,
@@ -379,8 +388,13 @@ pub(crate) fn deref_to_vir<'tcx>(
379388

380389
let typ_args = mk_typ_args(bctx, node_substs, trait_fun_id, span)?;
381390
let impl_paths = get_impl_paths(bctx, trait_fun_id, node_substs, None, false, span)?;
391+
let call_target_attrs = vir::ast::CallTargetAttrs {
392+
autospec: autospec_usage,
393+
assume_external_allowed: false,
394+
const_var: false,
395+
};
382396
let call_target =
383-
CallTarget::Fun(target_kind, trait_fun, typ_args, impl_paths, autospec_usage, false);
397+
CallTarget::Fun(target_kind, trait_fun, typ_args, impl_paths, call_target_attrs);
384398
let args = Arc::new(vec![arg.clone()]);
385399
let x = ExprX::Call(call_target, args, None);
386400

@@ -2815,12 +2829,12 @@ fn record_loop_spec<'tcx>(
28152829

28162830
pub(crate) fn record_call<'tcx>(bctx: &BodyCtxt<'tcx>, expr: &Expr, resolved_call: ResolvedCall) {
28172831
let resolved_call = match (resolved_call, &bctx.external_trait_from_to) {
2818-
(ResolvedCall::Call(ufun, rfun, in_ghost), Some(paths)) if paths.2.is_some() => {
2832+
(ResolvedCall::Call(ufun, rfun, in_ghost, ae), Some(paths)) if paths.2.is_some() => {
28192833
let (from_path, _to_path, to_spec_path) = &**paths;
28202834
use vir::traits::rewrite_fun;
28212835
let ufun = rewrite_fun(from_path, to_spec_path.as_ref().unwrap(), &ufun);
28222836
let rfun = rewrite_fun(from_path, to_spec_path.as_ref().unwrap(), &rfun);
2823-
ResolvedCall::Call(ufun, rfun, in_ghost)
2837+
ResolvedCall::Call(ufun, rfun, in_ghost, ae)
28242838
}
28252839
(resolved_call, _) => resolved_call,
28262840
};

source/rust_verify/src/rust_to_vir_expr.rs

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,13 +1462,17 @@ pub(crate) fn expr_to_vir_with_adjustments<'tcx>(
14621462
if let Some((fun, typ_args)) = f {
14631463
let autospec_usage =
14641464
if bctx.in_ghost { AutospecUsage::IfMarked } else { AutospecUsage::Final };
1465+
let call_target_attrs = vir::ast::CallTargetAttrs {
1466+
autospec: autospec_usage,
1467+
const_var: false,
1468+
assume_external_allowed: false,
1469+
};
14651470
let call_target = CallTarget::Fun(
14661471
vir::ast::CallTargetKind::Static,
14671472
fun,
14681473
typ_args,
14691474
Arc::new(vec![]),
1470-
autospec_usage,
1471-
false,
1475+
call_target_attrs,
14721476
);
14731477
let arg = arg.consume(bctx, get_inner_ty());
14741478
let args = Arc::new(vec![arg.clone()]);
@@ -2071,14 +2075,18 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
20712075
)?;
20722076

20732077
let typ_args = Arc::new(vec![tup_typ, ret_typ, fun_typ]);
2078+
let call_target_attrs = vir::ast::CallTargetAttrs {
2079+
autospec: AutospecUsage::Final,
2080+
const_var: false,
2081+
assume_external_allowed: false,
2082+
};
20742083
(
20752084
CallTarget::Fun(
20762085
kind,
20772086
helper_fun,
20782087
typ_args,
20792088
impl_paths,
2080-
AutospecUsage::Final,
2081-
false,
2089+
call_target_attrs,
20822090
),
20832091
vec![vir_fun, tup],
20842092
rcall,
@@ -2134,13 +2142,17 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
21342142
};
21352143
let autospec_usage =
21362144
if bctx.in_ghost { AutospecUsage::IfMarked } else { AutospecUsage::Final };
2145+
let call_target_attrs = vir::ast::CallTargetAttrs {
2146+
autospec: autospec_usage,
2147+
const_var: false,
2148+
assume_external_allowed: false,
2149+
};
21372150
let call_target = CallTarget::Fun(
21382151
vir::ast::CallTargetKind::Static,
21392152
fun,
21402153
typ_args,
21412154
Arc::new(vec![]),
2142-
autospec_usage,
2143-
false,
2155+
call_target_attrs,
21442156
);
21452157
let args = Arc::new(vec![arg_vir.clone()]);
21462158
mk_expr(ExprX::Call(call_target, args, None))
@@ -2231,13 +2243,17 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
22312243
let typ_args = Arc::new(vec![from_typ, to_typ]);
22322244
let autospec_usage =
22332245
if bctx.in_ghost { AutospecUsage::IfMarked } else { AutospecUsage::Final };
2246+
let call_target_attrs = vir::ast::CallTargetAttrs {
2247+
autospec: autospec_usage,
2248+
const_var: false,
2249+
assume_external_allowed: false,
2250+
};
22342251
let call_target = CallTarget::Fun(
22352252
vir::ast::CallTargetKind::Static,
22362253
fun,
22372254
typ_args,
22382255
Arc::new(vec![]),
2239-
autospec_usage,
2240-
false,
2256+
call_target_attrs,
22412257
);
22422258
let args = Arc::new(vec![source_vir_expr.clone()]);
22432259
mk_expr(ExprX::Call(call_target, args, None))
@@ -2992,6 +3008,11 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
29923008
None
29933009
};
29943010

3011+
let call_target_attrs = vir::ast::CallTargetAttrs {
3012+
autospec: AutospecUsage::Final,
3013+
const_var: false,
3014+
assume_external_allowed: false,
3015+
};
29953016
let call_target = if let Some((fun, typ_args)) = fun_typ_args {
29963017
// special fast path
29973018
CallTarget::Fun(
@@ -3001,8 +3022,7 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
30013022
// arbitrary impl_path
30023023
// REVIEW: why is this needed?
30033024
Arc::new(vec![ImplPath::TraitImplPath(vir::def::prefix_spec_fn_type(0))]),
3004-
AutospecUsage::Final,
3005-
false,
3025+
call_target_attrs,
30063026
)
30073027
} else {
30083028
// general Index trait case
@@ -3011,14 +3031,7 @@ pub(crate) fn expr_to_vir_innermost<'tcx>(
30113031
let typ_args =
30123032
Arc::new(vec![undecorate_typ(&tgt_vir.typ), idx_vir.typ.clone()]);
30133033
let fun = vir::fun!(CrateId::Core => "ops", "index", "Index", "index");
3014-
CallTarget::Fun(
3015-
target_kind,
3016-
fun,
3017-
typ_args,
3018-
impl_paths,
3019-
AutospecUsage::Final,
3020-
false,
3021-
)
3034+
CallTarget::Fun(target_kind, fun, typ_args, impl_paths, call_target_attrs)
30223035
};
30233036

30243037
// tgt[idx] is equivalent to either *index(tgt, idx) or *index_mut(tgt, idx)
@@ -3842,13 +3855,17 @@ pub(crate) fn maybe_do_ptr_cast<'tcx>(
38423855
Some(PtrCastKind::Complex(fun, typ_args, clip)) => {
38433856
let autospec_usage =
38443857
if bctx.in_ghost { AutospecUsage::IfMarked } else { AutospecUsage::Final };
3858+
let call_target_attrs = vir::ast::CallTargetAttrs {
3859+
autospec: autospec_usage,
3860+
const_var: false,
3861+
assume_external_allowed: false,
3862+
};
38453863
let call_target = CallTarget::Fun(
38463864
vir::ast::CallTargetKind::Static,
38473865
fun,
38483866
typ_args,
38493867
Arc::new(vec![]),
3850-
autospec_usage,
3851-
false,
3868+
call_target_attrs,
38523869
);
38533870
let args = Arc::new(vec![src_vir.clone()]);
38543871
let x = ExprX::Call(call_target, args, None);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#![feature(rustc_private)]
2+
#[macro_use]
3+
mod common;
4+
use common::*;
5+
6+
test_verify_one_file! {
7+
#[test] test_externals_available_without_declaration1 verus_code! {
8+
#[verifier::external]
9+
fn f0() {}
10+
11+
#[verifier::external]
12+
fn f1() -> u8 { 3 }
13+
14+
#[verifier::external]
15+
fn f2(u: &mut u8) -> u8 { 3 }
16+
17+
#[verifier::external]
18+
fn f3(u: u8) {}
19+
20+
assume_specification[ f3 ](u: u8)
21+
requires
22+
u > 10,
23+
;
24+
25+
#[verifier::assume(externals_available_without_declaration)]
26+
fn g0() {
27+
f0();
28+
f3(5); // FAILS
29+
}
30+
31+
#[verifier::assume(externals_available_without_declaration)]
32+
mod m {
33+
fn g0() {
34+
super::f0();
35+
}
36+
}
37+
38+
#[verifier::exec_allows_no_decreases_clause]
39+
#[verifier::assume(externals_available_without_declaration)]
40+
fn g1() {
41+
let mut x = 3;
42+
x = f1();
43+
assert(x >= 0);
44+
assert(x == 3); // FAILS
45+
loop {
46+
assert(x >= 0);
47+
break;
48+
}
49+
}
50+
51+
#[verifier::exec_allows_no_decreases_clause]
52+
#[verifier::assume(externals_available_without_declaration)]
53+
fn g2() {
54+
let mut x = 3;
55+
let u = f2(&mut x);
56+
assert(x >= 0);
57+
assert(u >= 0);
58+
assert(x == 3); // FAILS
59+
loop {
60+
assert(x >= 0);
61+
assert(u >= 0);
62+
break;
63+
}
64+
}
65+
} => Err(err) => assert_fails(err, 3)
66+
}
67+
68+
test_verify_one_file! {
69+
#[test] test_externals_available_without_declaration2 verus_code! {
70+
#[verifier::external]
71+
fn f0() {}
72+
73+
#[verifier::assume(externals_available_without_declaration)]
74+
mod m {
75+
#[verifier::deny(externals_available_without_declaration)]
76+
fn g0() {
77+
super::f0();
78+
}
79+
}
80+
} => Err(err) => assert_vir_error_msg(err, "cannot use function")
81+
}

0 commit comments

Comments
 (0)