Rollup merge of #128045 - pnkfelix:rustc-contracts, r=oli-obk
#[contracts::requires(...)] + #[contracts::ensures(...)] cc https://github.com/rust-lang/rust/issues/128044 Updated contract support: attribute syntax for preconditions and postconditions, implemented via a series of desugarings that culminates in: 1. a compile-time flag (`-Z contract-checks`) that, similar to `-Z ub-checks`, attempts to ensure that the decision of enabling/disabling contract checks is delayed until the end user program is compiled, 2. invocations of lang-items that handle invoking the precondition, building a checker for the post-condition, and invoking that post-condition checker at the return sites for the function, and 3. intrinsics for the actual evaluation of pre- and post-condition predicates that third-party verification tools can intercept and reinterpret for their own purposes (e.g. creating shims of behavior that abstract away the function body and replace it solely with the pre- and post-conditions). Known issues: * My original intent, as described in the MCP (https://github.com/rust-lang/compiler-team/issues/759) was to have a rustc-prefixed attribute namespace (like rustc_contracts::requires). But I could not get things working when I tried to do rewriting via a rustc-prefixed builtin attribute-macro. So for now it is called `contracts::requires`. * Our attribute macro machinery does not provide direct support for attribute arguments that are parsed like rust expressions. I spent some time trying to add that (e.g. something that would parse the attribute arguments as an AST while treating the remainder of the items as a token-tree), but its too big a lift for me to undertake. So instead I hacked in something approximating that goal, by semi-trivially desugaring the token-tree attribute contents into internal AST constucts. This may be too fragile for the long-term. * (In particular, it *definitely* breaks when you try to add a contract to a function like this: `fn foo1(x: i32) -> S<{ 23 }> { ... }`, because its token-tree based search for where to inject the internal AST constructs cannot immediately see that the `{ 23 }` is within a generics list. I think we can live for this for the short-term, i.e. land the work, and continue working on it while in parallel adding a new attribute variant that takes a token-tree attribute alongside an AST annotation, which would completely resolve the issue here.) * the *intent* of `-Z contract-checks` is that it behaves like `-Z ub-checks`, in that we do not prematurely commit to including or excluding the contract evaluation in upstream crates (most notably, `core` and `std`). But the current test suite does not actually *check* that this is the case. Ideally the test suite would be extended with a multi-crate test that explores the matrix of enabling/disabling contracts on both the upstream lib and final ("leaf") bin crates.
This commit is contained in:
commit
d81701b610
123 changed files with 1869 additions and 39 deletions
|
@ -3348,11 +3348,18 @@ pub struct Impl {
|
|||
pub items: ThinVec<P<AssocItem>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encodable, Decodable, Debug, Default)]
|
||||
pub struct FnContract {
|
||||
pub requires: Option<P<Expr>>,
|
||||
pub ensures: Option<P<Expr>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encodable, Decodable, Debug)]
|
||||
pub struct Fn {
|
||||
pub defaultness: Defaultness,
|
||||
pub generics: Generics,
|
||||
pub sig: FnSig,
|
||||
pub contract: Option<P<FnContract>>,
|
||||
pub body: Option<P<Block>>,
|
||||
}
|
||||
|
||||
|
@ -3650,7 +3657,7 @@ mod size_asserts {
|
|||
static_assert_size!(Block, 32);
|
||||
static_assert_size!(Expr, 72);
|
||||
static_assert_size!(ExprKind, 40);
|
||||
static_assert_size!(Fn, 160);
|
||||
static_assert_size!(Fn, 168);
|
||||
static_assert_size!(ForeignItem, 88);
|
||||
static_assert_size!(ForeignItemKind, 16);
|
||||
static_assert_size!(GenericArg, 24);
|
||||
|
|
|
@ -143,6 +143,10 @@ pub trait MutVisitor: Sized {
|
|||
walk_flat_map_assoc_item(self, i, ctxt)
|
||||
}
|
||||
|
||||
fn visit_contract(&mut self, c: &mut P<FnContract>) {
|
||||
walk_contract(self, c);
|
||||
}
|
||||
|
||||
fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
|
||||
walk_fn_decl(self, d);
|
||||
}
|
||||
|
@ -958,13 +962,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
|
|||
_ctxt,
|
||||
_ident,
|
||||
_vis,
|
||||
Fn { defaultness, generics, body, sig: FnSig { header, decl, span } },
|
||||
Fn { defaultness, generics, contract, body, sig: FnSig { header, decl, span } },
|
||||
) => {
|
||||
// Identifier and visibility are visited as a part of the item.
|
||||
visit_defaultness(vis, defaultness);
|
||||
vis.visit_fn_header(header);
|
||||
vis.visit_generics(generics);
|
||||
vis.visit_fn_decl(decl);
|
||||
if let Some(contract) = contract {
|
||||
vis.visit_contract(contract);
|
||||
}
|
||||
if let Some(body) = body {
|
||||
vis.visit_block(body);
|
||||
}
|
||||
|
@ -979,6 +986,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) {
|
||||
let FnContract { requires, ensures } = contract.deref_mut();
|
||||
if let Some(pred) = requires {
|
||||
vis.visit_expr(pred);
|
||||
}
|
||||
if let Some(pred) = ensures {
|
||||
vis.visit_expr(pred);
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
|
||||
let FnDecl { inputs, output } = decl.deref_mut();
|
||||
inputs.flat_map_in_place(|param| vis.flat_map_param(param));
|
||||
|
|
|
@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized {
|
|||
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
|
||||
walk_closure_binder(self, b)
|
||||
}
|
||||
fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result {
|
||||
walk_contract(self, c)
|
||||
}
|
||||
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
|
||||
walk_where_predicate(self, p)
|
||||
}
|
||||
|
@ -800,6 +803,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
|
|||
V::Result::output()
|
||||
}
|
||||
|
||||
pub fn walk_contract<'a, V: Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result {
|
||||
let FnContract { requires, ensures } = c;
|
||||
if let Some(pred) = requires {
|
||||
visitor.visit_expr(pred);
|
||||
}
|
||||
if let Some(pred) = ensures {
|
||||
visitor.visit_expr(pred);
|
||||
}
|
||||
V::Result::output()
|
||||
}
|
||||
|
||||
pub fn walk_where_predicate<'a, V: Visitor<'a>>(
|
||||
visitor: &mut V,
|
||||
predicate: &'a WherePredicate,
|
||||
|
@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
|
|||
_ctxt,
|
||||
_ident,
|
||||
_vis,
|
||||
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, body },
|
||||
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, contract, body },
|
||||
) => {
|
||||
// Identifier and visibility are visited as a part of the item.
|
||||
try_visit!(visitor.visit_fn_header(header));
|
||||
try_visit!(visitor.visit_generics(generics));
|
||||
try_visit!(visitor.visit_fn_decl(decl));
|
||||
visit_opt!(visitor, visit_contract, contract);
|
||||
visit_opt!(visitor, visit_block, body);
|
||||
}
|
||||
FnKind::Closure(binder, coroutine_kind, decl, body) => {
|
||||
|
|
|
@ -311,8 +311,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
|
||||
}
|
||||
ExprKind::Ret(e) => {
|
||||
let e = e.as_ref().map(|x| self.lower_expr(x));
|
||||
hir::ExprKind::Ret(e)
|
||||
let expr = e.as_ref().map(|x| self.lower_expr(x));
|
||||
self.checked_return(expr)
|
||||
}
|
||||
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
|
||||
ExprKind::Become(sub_expr) => {
|
||||
|
@ -379,6 +379,32 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Create an `ExprKind::Ret` that is preceded by a call to check contract ensures clause.
|
||||
fn checked_return(&mut self, opt_expr: Option<&'hir hir::Expr<'hir>>) -> hir::ExprKind<'hir> {
|
||||
let checked_ret = if let Some(Some((span, fresh_ident))) =
|
||||
self.contract.as_ref().map(|c| c.ensures.as_ref().map(|e| (e.expr.span, e.fresh_ident)))
|
||||
{
|
||||
let expr = opt_expr.unwrap_or_else(|| self.expr_unit(span));
|
||||
Some(self.inject_ensures_check(expr, span, fresh_ident.0, fresh_ident.2))
|
||||
} else {
|
||||
opt_expr
|
||||
};
|
||||
hir::ExprKind::Ret(checked_ret)
|
||||
}
|
||||
|
||||
/// Wraps an expression with a call to the ensures check before it gets returned.
|
||||
pub(crate) fn inject_ensures_check(
|
||||
&mut self,
|
||||
expr: &'hir hir::Expr<'hir>,
|
||||
span: Span,
|
||||
check_ident: Ident,
|
||||
check_hir_id: HirId,
|
||||
) -> &'hir hir::Expr<'hir> {
|
||||
let checker_fn = self.expr_ident(span, check_ident, check_hir_id);
|
||||
let span = self.mark_span_with_reason(DesugaringKind::Contract, span, None);
|
||||
self.expr_call(span, checker_fn, std::slice::from_ref(expr))
|
||||
}
|
||||
|
||||
pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {
|
||||
self.with_new_scopes(c.value.span, |this| {
|
||||
let def_id = this.local_def_id(c.id);
|
||||
|
@ -1991,7 +2017,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
),
|
||||
))
|
||||
} else {
|
||||
self.arena.alloc(self.expr(try_span, hir::ExprKind::Ret(Some(from_residual_expr))))
|
||||
let ret_expr = self.checked_return(Some(from_residual_expr));
|
||||
self.arena.alloc(self.expr(try_span, ret_expr))
|
||||
};
|
||||
self.lower_attrs(ret_expr.hir_id, &attrs);
|
||||
|
||||
|
@ -2040,7 +2067,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
let target_id = Ok(catch_id);
|
||||
hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr))
|
||||
} else {
|
||||
hir::ExprKind::Ret(Some(from_yeet_expr))
|
||||
self.checked_return(Some(from_yeet_expr))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -207,9 +207,40 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
sig: FnSig { decl, header, span: fn_sig_span },
|
||||
generics,
|
||||
body,
|
||||
contract,
|
||||
..
|
||||
}) => {
|
||||
self.with_new_scopes(*fn_sig_span, |this| {
|
||||
assert!(this.contract.is_none());
|
||||
if let Some(contract) = contract {
|
||||
let requires = contract.requires.clone();
|
||||
let ensures = contract.ensures.clone();
|
||||
let ensures = ensures.map(|ens| {
|
||||
// FIXME: this needs to be a fresh (or illegal) identifier to prevent
|
||||
// accidental capture of a parameter or global variable.
|
||||
let checker_ident: Ident =
|
||||
Ident::from_str_and_span("__ensures_checker", ens.span);
|
||||
let (checker_pat, checker_hir_id) = this.pat_ident_binding_mode_mut(
|
||||
ens.span,
|
||||
checker_ident,
|
||||
hir::BindingMode::NONE,
|
||||
);
|
||||
|
||||
crate::FnContractLoweringEnsures {
|
||||
expr: ens,
|
||||
fresh_ident: (checker_ident, checker_pat, checker_hir_id),
|
||||
}
|
||||
});
|
||||
|
||||
// Note: `with_new_scopes` will reinstall the outer
|
||||
// item's contract (if any) after its callback finishes.
|
||||
this.contract.replace(crate::FnContractLoweringInfo {
|
||||
span,
|
||||
requires,
|
||||
ensures,
|
||||
});
|
||||
}
|
||||
|
||||
// Note: we don't need to change the return type from `T` to
|
||||
// `impl Future<Output = T>` here because lower_body
|
||||
// only cares about the input argument patterns in the function
|
||||
|
@ -1054,10 +1085,64 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
|
||||
) -> hir::BodyId {
|
||||
self.lower_body(|this| {
|
||||
(
|
||||
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))),
|
||||
body(this),
|
||||
)
|
||||
let params =
|
||||
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
|
||||
let result = body(this);
|
||||
|
||||
let opt_contract = this.contract.take();
|
||||
|
||||
// { body }
|
||||
// ==>
|
||||
// { contract_requires(PRECOND); { body } }
|
||||
let Some(contract) = opt_contract else { return (params, result) };
|
||||
let result_ref = this.arena.alloc(result);
|
||||
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
|
||||
this.expr(contract.span, hir::ExprKind::Tup(&[]))
|
||||
};
|
||||
|
||||
let precond: hir::Stmt<'hir> = if let Some(req) = contract.requires {
|
||||
let lowered_req = this.lower_expr_mut(&req);
|
||||
let precond = this.expr_call_lang_item_fn_mut(
|
||||
req.span,
|
||||
hir::LangItem::ContractCheckRequires,
|
||||
&*arena_vec![this; lowered_req],
|
||||
);
|
||||
this.stmt_expr(req.span, precond)
|
||||
} else {
|
||||
let u = lit_unit(this);
|
||||
this.stmt_expr(contract.span, u)
|
||||
};
|
||||
|
||||
let (postcond_checker, result) = if let Some(ens) = contract.ensures {
|
||||
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
|
||||
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
|
||||
let postcond_checker = this.expr_call_lang_item_fn(
|
||||
ens.span,
|
||||
hir::LangItem::ContractBuildCheckEnsures,
|
||||
&*arena_vec![this; lowered_ens],
|
||||
);
|
||||
let checker_binding_pat = fresh_ident.1;
|
||||
(
|
||||
this.stmt_let_pat(
|
||||
None,
|
||||
ens.span,
|
||||
Some(postcond_checker),
|
||||
this.arena.alloc(checker_binding_pat),
|
||||
hir::LocalSource::Contract,
|
||||
),
|
||||
this.inject_ensures_check(result_ref, ens.span, fresh_ident.0, fresh_ident.2),
|
||||
)
|
||||
} else {
|
||||
let u = lit_unit(this);
|
||||
(this.stmt_expr(contract.span, u), &*result_ref)
|
||||
};
|
||||
|
||||
let block = this.block_all(
|
||||
contract.span,
|
||||
arena_vec![this; precond, postcond_checker],
|
||||
Some(result),
|
||||
);
|
||||
(params, this.expr_block(block))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,19 @@ mod path;
|
|||
|
||||
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FnContractLoweringInfo<'hir> {
|
||||
pub span: Span,
|
||||
pub requires: Option<ast::ptr::P<ast::Expr>>,
|
||||
pub ensures: Option<FnContractLoweringEnsures<'hir>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct FnContractLoweringEnsures<'hir> {
|
||||
expr: ast::ptr::P<ast::Expr>,
|
||||
fresh_ident: (Ident, hir::Pat<'hir>, HirId),
|
||||
}
|
||||
|
||||
struct LoweringContext<'a, 'hir> {
|
||||
tcx: TyCtxt<'hir>,
|
||||
resolver: &'a mut ResolverAstLowering,
|
||||
|
@ -100,6 +113,8 @@ struct LoweringContext<'a, 'hir> {
|
|||
/// Collect items that were created by lowering the current owner.
|
||||
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,
|
||||
|
||||
contract: Option<FnContractLoweringInfo<'hir>>,
|
||||
|
||||
coroutine_kind: Option<hir::CoroutineKind>,
|
||||
|
||||
/// When inside an `async` context, this is the `HirId` of the
|
||||
|
@ -148,6 +163,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
bodies: Vec::new(),
|
||||
attrs: SortedMap::default(),
|
||||
children: Vec::default(),
|
||||
contract: None,
|
||||
current_hir_id_owner: hir::CRATE_OWNER_ID,
|
||||
item_local_id_counter: hir::ItemLocalId::ZERO,
|
||||
ident_and_label_to_local_id: Default::default(),
|
||||
|
@ -834,12 +850,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
let was_in_loop_condition = self.is_in_loop_condition;
|
||||
self.is_in_loop_condition = false;
|
||||
|
||||
let old_contract = self.contract.take();
|
||||
|
||||
let catch_scope = self.catch_scope.take();
|
||||
let loop_scope = self.loop_scope.take();
|
||||
let ret = f(self);
|
||||
self.catch_scope = catch_scope;
|
||||
self.loop_scope = loop_scope;
|
||||
|
||||
self.contract = old_contract;
|
||||
|
||||
self.is_in_loop_condition = was_in_loop_condition;
|
||||
|
||||
self.current_item = current_item;
|
||||
|
|
|
@ -917,7 +917,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
|
|||
walk_list!(self, visit_attribute, &item.attrs);
|
||||
return; // Avoid visiting again.
|
||||
}
|
||||
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, body }) => {
|
||||
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, contract: _, body }) => {
|
||||
self.check_defaultness(item.span, *defaultness);
|
||||
|
||||
let is_intrinsic =
|
||||
|
|
|
@ -548,6 +548,8 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
|
|||
gate_all!(pin_ergonomics, "pinned reference syntax is experimental");
|
||||
gate_all!(unsafe_fields, "`unsafe` fields are experimental");
|
||||
gate_all!(unsafe_binders, "unsafe binder types are experimental");
|
||||
gate_all!(contracts, "contracts are incomplete");
|
||||
gate_all!(contracts_internals, "contract internal machinery is for internal use only");
|
||||
|
||||
if !visitor.features.never_patterns() {
|
||||
if let Some(spans) = spans.get(&sym::never_patterns) {
|
||||
|
|
|
@ -650,13 +650,17 @@ impl<'a> State<'a> {
|
|||
attrs: &[ast::Attribute],
|
||||
func: &ast::Fn,
|
||||
) {
|
||||
let ast::Fn { defaultness, generics, sig, body } = func;
|
||||
let ast::Fn { defaultness, generics, sig, contract, body } = func;
|
||||
if body.is_some() {
|
||||
self.head("");
|
||||
}
|
||||
self.print_visibility(vis);
|
||||
self.print_defaultness(*defaultness);
|
||||
self.print_fn(&sig.decl, sig.header, Some(name), generics);
|
||||
if let Some(contract) = &contract {
|
||||
self.nbsp();
|
||||
self.print_contract(contract);
|
||||
}
|
||||
if let Some(body) = body {
|
||||
self.nbsp();
|
||||
self.print_block_with_attrs(body, attrs);
|
||||
|
@ -665,6 +669,21 @@ impl<'a> State<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_contract(&mut self, contract: &ast::FnContract) {
|
||||
if let Some(pred) = &contract.requires {
|
||||
self.word("rustc_requires");
|
||||
self.popen();
|
||||
self.print_expr(pred, FixupContext::default());
|
||||
self.pclose();
|
||||
}
|
||||
if let Some(pred) = &contract.ensures {
|
||||
self.word("rustc_ensures");
|
||||
self.popen();
|
||||
self.print_expr(pred, FixupContext::default());
|
||||
self.pclose();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn print_fn(
|
||||
&mut self,
|
||||
decl: &ast::FnDecl,
|
||||
|
|
|
@ -1653,6 +1653,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
ConstraintCategory::SizedBound,
|
||||
);
|
||||
}
|
||||
&Rvalue::NullaryOp(NullOp::ContractChecks, _) => {}
|
||||
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
|
||||
|
||||
Rvalue::ShallowInitBox(operand, ty) => {
|
||||
|
|
|
@ -85,6 +85,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span
|
|||
defaultness: ast::Defaultness::Final,
|
||||
sig,
|
||||
generics: Generics::default(),
|
||||
contract: None,
|
||||
body,
|
||||
}));
|
||||
|
||||
|
|
176
compiler/rustc_builtin_macros/src/contracts.rs
Normal file
176
compiler/rustc_builtin_macros/src/contracts.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
#![allow(unused_imports, unused_variables)]
|
||||
|
||||
use rustc_ast::token;
|
||||
use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_expand::base::{AttrProcMacro, ExtCtxt};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::{Ident, Symbol, kw, sym};
|
||||
|
||||
pub(crate) struct ExpandRequires;
|
||||
|
||||
pub(crate) struct ExpandEnsures;
|
||||
|
||||
impl AttrProcMacro for ExpandRequires {
|
||||
fn expand<'cx>(
|
||||
&self,
|
||||
ecx: &'cx mut ExtCtxt<'_>,
|
||||
span: Span,
|
||||
annotation: TokenStream,
|
||||
annotated: TokenStream,
|
||||
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||
expand_requires_tts(ecx, span, annotation, annotated)
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrProcMacro for ExpandEnsures {
|
||||
fn expand<'cx>(
|
||||
&self,
|
||||
ecx: &'cx mut ExtCtxt<'_>,
|
||||
span: Span,
|
||||
annotation: TokenStream,
|
||||
annotated: TokenStream,
|
||||
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||
expand_ensures_tts(ecx, span, annotation, annotated)
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand the function signature to include the contract clause.
|
||||
///
|
||||
/// The contracts clause will be injected before the function body and the optional where clause.
|
||||
/// For that, we search for the body / where token, and invoke the `inject` callback to generate the
|
||||
/// contract clause in the right place.
|
||||
///
|
||||
// FIXME: this kind of manual token tree munging does not have significant precedent among
|
||||
// rustc builtin macros, probably because most builtin macros use direct AST manipulation to
|
||||
// accomplish similar goals. But since our attributes need to take arbitrary expressions, and
|
||||
// our attribute infrastructure does not yet support mixing a token-tree annotation with an AST
|
||||
// annotated, we end up doing token tree manipulation.
|
||||
fn expand_contract_clause(
|
||||
ecx: &mut ExtCtxt<'_>,
|
||||
attr_span: Span,
|
||||
annotated: TokenStream,
|
||||
inject: impl FnOnce(&mut TokenStream) -> Result<(), ErrorGuaranteed>,
|
||||
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||
let mut new_tts = TokenStream::default();
|
||||
let mut cursor = annotated.iter();
|
||||
|
||||
let is_kw = |tt: &TokenTree, sym: Symbol| {
|
||||
if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false }
|
||||
};
|
||||
|
||||
// Find the `fn` keyword to check if this is a function.
|
||||
if cursor
|
||||
.find(|tt| {
|
||||
new_tts.push_tree((*tt).clone());
|
||||
is_kw(tt, kw::Fn)
|
||||
})
|
||||
.is_none()
|
||||
{
|
||||
return Err(ecx
|
||||
.sess
|
||||
.dcx()
|
||||
.span_err(attr_span, "contract annotations can only be used on functions"));
|
||||
}
|
||||
|
||||
// Found the `fn` keyword, now find either the `where` token or the function body.
|
||||
let next_tt = loop {
|
||||
let Some(tt) = cursor.next() else {
|
||||
return Err(ecx.sess.dcx().span_err(
|
||||
attr_span,
|
||||
"contract annotations is only supported in functions with bodies",
|
||||
));
|
||||
};
|
||||
// If `tt` is the last element. Check if it is the function body.
|
||||
if cursor.peek().is_none() {
|
||||
if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt {
|
||||
break tt;
|
||||
} else {
|
||||
return Err(ecx.sess.dcx().span_err(
|
||||
attr_span,
|
||||
"contract annotations is only supported in functions with bodies",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if is_kw(tt, kw::Where) {
|
||||
break tt;
|
||||
}
|
||||
new_tts.push_tree(tt.clone());
|
||||
};
|
||||
|
||||
// At this point, we've transcribed everything from the `fn` through the formal parameter list
|
||||
// and return type declaration, (if any), but `tt` itself has *not* been transcribed.
|
||||
//
|
||||
// Now inject the AST contract form.
|
||||
//
|
||||
inject(&mut new_tts)?;
|
||||
|
||||
// Above we injected the internal AST requires/ensures construct. Now copy over all the other
|
||||
// token trees.
|
||||
new_tts.push_tree(next_tt.clone());
|
||||
while let Some(tt) = cursor.next() {
|
||||
new_tts.push_tree(tt.clone());
|
||||
if cursor.peek().is_none()
|
||||
&& !matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _))
|
||||
{
|
||||
return Err(ecx.sess.dcx().span_err(
|
||||
attr_span,
|
||||
"contract annotations is only supported in functions with bodies",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Record the span as a contract attribute expansion.
|
||||
// This is used later to stop users from using the extended syntax directly
|
||||
// which is gated via `contracts_internals`.
|
||||
ecx.psess().contract_attribute_spans.push(attr_span);
|
||||
|
||||
Ok(new_tts)
|
||||
}
|
||||
|
||||
fn expand_requires_tts(
|
||||
_ecx: &mut ExtCtxt<'_>,
|
||||
attr_span: Span,
|
||||
annotation: TokenStream,
|
||||
annotated: TokenStream,
|
||||
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||
expand_contract_clause(_ecx, attr_span, annotated, |new_tts| {
|
||||
new_tts.push_tree(TokenTree::Token(
|
||||
token::Token::from_ast_ident(Ident::new(kw::ContractRequires, attr_span)),
|
||||
Spacing::Joint,
|
||||
));
|
||||
new_tts.push_tree(TokenTree::Token(
|
||||
token::Token::new(token::TokenKind::OrOr, attr_span),
|
||||
Spacing::Alone,
|
||||
));
|
||||
new_tts.push_tree(TokenTree::Delimited(
|
||||
DelimSpan::from_single(attr_span),
|
||||
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
|
||||
token::Delimiter::Parenthesis,
|
||||
annotation,
|
||||
));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_ensures_tts(
|
||||
_ecx: &mut ExtCtxt<'_>,
|
||||
attr_span: Span,
|
||||
annotation: TokenStream,
|
||||
annotated: TokenStream,
|
||||
) -> Result<TokenStream, ErrorGuaranteed> {
|
||||
expand_contract_clause(_ecx, attr_span, annotated, |new_tts| {
|
||||
new_tts.push_tree(TokenTree::Token(
|
||||
token::Token::from_ast_ident(Ident::new(kw::ContractEnsures, attr_span)),
|
||||
Spacing::Joint,
|
||||
));
|
||||
new_tts.push_tree(TokenTree::Delimited(
|
||||
DelimSpan::from_single(attr_span),
|
||||
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
|
||||
token::Delimiter::Parenthesis,
|
||||
annotation,
|
||||
));
|
||||
Ok(())
|
||||
})
|
||||
}
|
|
@ -1034,6 +1034,7 @@ impl<'a> MethodDef<'a> {
|
|||
defaultness,
|
||||
sig,
|
||||
generics: fn_generics,
|
||||
contract: None,
|
||||
body: Some(body_block),
|
||||
})),
|
||||
tokens: None,
|
||||
|
|
|
@ -81,6 +81,7 @@ impl AllocFnFactory<'_, '_> {
|
|||
defaultness: ast::Defaultness::Final,
|
||||
sig,
|
||||
generics: Generics::default(),
|
||||
contract: None,
|
||||
body,
|
||||
}));
|
||||
let item = self.cx.item(
|
||||
|
|
|
@ -55,6 +55,7 @@ mod trace_macros;
|
|||
|
||||
pub mod asm;
|
||||
pub mod cmdline_attrs;
|
||||
pub mod contracts;
|
||||
pub mod proc_macro_harness;
|
||||
pub mod standard_library_imports;
|
||||
pub mod test_harness;
|
||||
|
@ -137,4 +138,8 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
|
|||
|
||||
let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
|
||||
register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
|
||||
let requires = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandRequires));
|
||||
register(sym::contracts_requires, requires);
|
||||
let ensures = SyntaxExtensionKind::Attr(Box::new(contracts::ExpandEnsures));
|
||||
register(sym::contracts_ensures, ensures);
|
||||
}
|
||||
|
|
|
@ -344,6 +344,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
|||
defaultness,
|
||||
sig,
|
||||
generics: ast::Generics::default(),
|
||||
contract: None,
|
||||
body: Some(main_body),
|
||||
}));
|
||||
|
||||
|
|
|
@ -868,7 +868,16 @@ fn codegen_stmt<'tcx>(
|
|||
NullOp::UbChecks => {
|
||||
let val = fx.tcx.sess.ub_checks();
|
||||
let val = CValue::by_val(
|
||||
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
|
||||
fx.bcx.ins().iconst(types::I8, i64::from(val)),
|
||||
fx.layout_of(fx.tcx.types.bool),
|
||||
);
|
||||
lval.write_cvalue(fx, val);
|
||||
return;
|
||||
}
|
||||
NullOp::ContractChecks => {
|
||||
let val = fx.tcx.sess.contract_checks();
|
||||
let val = CValue::by_val(
|
||||
fx.bcx.ins().iconst(types::I8, i64::from(val)),
|
||||
fx.layout_of(fx.tcx.types.bool),
|
||||
);
|
||||
lval.write_cvalue(fx, val);
|
||||
|
|
|
@ -741,6 +741,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let val = bx.tcx().sess.ub_checks();
|
||||
bx.cx().const_bool(val)
|
||||
}
|
||||
mir::NullOp::ContractChecks => {
|
||||
let val = bx.tcx().sess.contract_checks();
|
||||
bx.cx().const_bool(val)
|
||||
}
|
||||
};
|
||||
let tcx = self.cx.tcx();
|
||||
OperandRef {
|
||||
|
|
|
@ -675,7 +675,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
|||
Rvalue::Cast(_, _, _) => {}
|
||||
|
||||
Rvalue::NullaryOp(
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks,
|
||||
NullOp::SizeOf
|
||||
| NullOp::AlignOf
|
||||
| NullOp::OffsetOf(_)
|
||||
| NullOp::UbChecks
|
||||
| NullOp::ContractChecks,
|
||||
_,
|
||||
) => {}
|
||||
Rvalue::ShallowInitBox(_, _) => {}
|
||||
|
|
|
@ -293,6 +293,9 @@ pub trait Machine<'tcx>: Sized {
|
|||
/// Determines the result of a `NullaryOp::UbChecks` invocation.
|
||||
fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>;
|
||||
|
||||
/// Determines the result of a `NullaryOp::ContractChecks` invocation.
|
||||
fn contract_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>;
|
||||
|
||||
/// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction.
|
||||
/// You can use this to detect long or endlessly running programs.
|
||||
#[inline]
|
||||
|
@ -679,6 +682,13 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
|
|||
interp_ok(true)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contract_checks(_ecx: &InterpCx<$tcx, Self>) -> InterpResult<$tcx, bool> {
|
||||
// We can't look at `tcx.sess` here as that can differ across crates, which can lead to
|
||||
// unsound differences in evaluating the same constant at different instantiation sites.
|
||||
interp_ok(true)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn adjust_global_allocation<'b>(
|
||||
_ecx: &InterpCx<$tcx, Self>,
|
||||
|
|
|
@ -537,6 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
ImmTy::from_uint(val, usize_layout())
|
||||
}
|
||||
UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx),
|
||||
ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ const GATED_CFGS: &[GatedCfg] = &[
|
|||
// (name in cfg, feature, function to check if the feature is enabled)
|
||||
(sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks),
|
||||
(sym::ub_checks, sym::cfg_ub_checks, Features::cfg_ub_checks),
|
||||
(sym::contract_checks, sym::cfg_contract_checks, Features::cfg_contract_checks),
|
||||
(sym::target_thread_local, sym::cfg_target_thread_local, Features::cfg_target_thread_local),
|
||||
(
|
||||
sym::target_has_atomic_equal_alignment,
|
||||
|
|
|
@ -403,6 +403,8 @@ declare_features! (
|
|||
(unstable, c_variadic, "1.34.0", Some(44930)),
|
||||
/// Allows the use of `#[cfg(<true/false>)]`.
|
||||
(unstable, cfg_boolean_literals, "1.83.0", Some(131204)),
|
||||
/// Allows the use of `#[cfg(contract_checks)` to check if contract checks are enabled.
|
||||
(unstable, cfg_contract_checks, "CURRENT_RUSTC_VERSION", Some(128044)),
|
||||
/// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour.
|
||||
(unstable, cfg_overflow_checks, "1.71.0", Some(111466)),
|
||||
/// Provides the relocation model information as cfg entry
|
||||
|
@ -445,6 +447,10 @@ declare_features! (
|
|||
(unstable, const_trait_impl, "1.42.0", Some(67792)),
|
||||
/// Allows the `?` operator in const contexts.
|
||||
(unstable, const_try, "1.56.0", Some(74935)),
|
||||
/// Allows use of contracts attributes.
|
||||
(incomplete, contracts, "CURRENT_RUSTC_VERSION", Some(128044)),
|
||||
/// Allows access to internal machinery used to implement contracts.
|
||||
(internal, contracts_internals, "CURRENT_RUSTC_VERSION", Some(128044)),
|
||||
/// Allows coroutines to be cloned.
|
||||
(unstable, coroutine_clone, "1.65.0", Some(95360)),
|
||||
/// Allows defining coroutines.
|
||||
|
|
|
@ -2654,6 +2654,8 @@ pub enum LocalSource {
|
|||
/// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression.
|
||||
/// The span is that of the `=` sign.
|
||||
AssignDesugar(Span),
|
||||
/// A contract `#[ensures(..)]` attribute injects a let binding for the check that runs at point of return.
|
||||
Contract,
|
||||
}
|
||||
|
||||
/// Hints at the original code for a `match _ { .. }`.
|
||||
|
|
|
@ -423,6 +423,10 @@ language_item_table! {
|
|||
|
||||
String, sym::String, string, Target::Struct, GenericRequirement::None;
|
||||
CStr, sym::CStr, c_str, Target::Struct, GenericRequirement::None;
|
||||
|
||||
// Experimental lang items for implementing contract pre- and post-condition checking.
|
||||
ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None;
|
||||
ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None;
|
||||
}
|
||||
|
||||
pub enum GenericRequirement {
|
||||
|
|
|
@ -132,6 +132,9 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
|
|||
| sym::aggregate_raw_ptr
|
||||
| sym::ptr_metadata
|
||||
| sym::ub_checks
|
||||
| sym::contract_checks
|
||||
| sym::contract_check_requires
|
||||
| sym::contract_check_ensures
|
||||
| sym::fadd_algebraic
|
||||
| sym::fsub_algebraic
|
||||
| sym::fmul_algebraic
|
||||
|
@ -219,6 +222,16 @@ pub fn check_intrinsic_type(
|
|||
}
|
||||
};
|
||||
(n_tps, 0, 0, inputs, output, hir::Safety::Unsafe)
|
||||
} else if intrinsic_name == sym::contract_check_ensures {
|
||||
// contract_check_ensures::<'a, Ret, C>(&'a Ret, C)
|
||||
// where C: impl Fn(&'a Ret) -> bool,
|
||||
//
|
||||
// so: two type params, one lifetime param, 0 const params, two inputs, no return
|
||||
|
||||
let p = generics.param_at(0, tcx);
|
||||
let r = ty::Region::new_early_param(tcx, p.to_early_bound_region_data());
|
||||
let ref_ret = Ty::new_imm_ref(tcx, r, param(1));
|
||||
(2, 1, 0, vec![ref_ret, param(2)], tcx.types.unit, hir::Safety::Safe)
|
||||
} else {
|
||||
let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
|
||||
let (n_tps, n_cts, inputs, output) = match intrinsic_name {
|
||||
|
@ -610,6 +623,11 @@ pub fn check_intrinsic_type(
|
|||
|
||||
sym::box_new => (1, 0, vec![param(0)], Ty::new_box(tcx, param(0))),
|
||||
|
||||
// contract_checks() -> bool
|
||||
sym::contract_checks => (0, 0, Vec::new(), tcx.types.bool),
|
||||
// contract_check_requires::<C>(C) -> bool, where C: impl Fn() -> bool
|
||||
sym::contract_check_requires => (1, 0, vec![param(0)], tcx.types.unit),
|
||||
|
||||
sym::simd_eq
|
||||
| sym::simd_ne
|
||||
| sym::simd_lt
|
||||
|
|
|
@ -269,6 +269,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// diverging expression (e.g. it arose from desugaring of `try { return }`),
|
||||
// we skip issuing a warning because it is autogenerated code.
|
||||
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
|
||||
// Likewise, do not lint unreachable code injected via contracts desugaring.
|
||||
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::Contract) => {}
|
||||
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
|
||||
ExprKind::MethodCall(segment, ..) => {
|
||||
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
|
||||
|
|
|
@ -1104,6 +1104,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
|
|||
NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
|
||||
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
|
||||
NullOp::UbChecks => write!(fmt, "UbChecks()"),
|
||||
NullOp::ContractChecks => write!(fmt, "ContractChecks()"),
|
||||
}
|
||||
}
|
||||
ThreadLocalRef(did) => ty::tls::with(|tcx| {
|
||||
|
|
|
@ -1591,6 +1591,9 @@ pub enum NullOp<'tcx> {
|
|||
/// Returns whether we should perform some UB-checking at runtime.
|
||||
/// See the `ub_checks` intrinsic docs for details.
|
||||
UbChecks,
|
||||
/// Returns whether we should perform contract-checking at runtime.
|
||||
/// See the `contract_checks` intrinsic docs for details.
|
||||
ContractChecks,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -230,7 +230,8 @@ impl<'tcx> Rvalue<'tcx> {
|
|||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
||||
tcx.types.usize
|
||||
}
|
||||
Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool,
|
||||
Rvalue::NullaryOp(NullOp::ContractChecks, _)
|
||||
| Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool,
|
||||
Rvalue::Aggregate(ref ak, ref ops) => match **ak {
|
||||
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
|
||||
AggregateKind::Tuple => {
|
||||
|
|
|
@ -417,7 +417,11 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
|
|||
| Rvalue::Discriminant(..)
|
||||
| Rvalue::Len(..)
|
||||
| Rvalue::NullaryOp(
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks,
|
||||
NullOp::SizeOf
|
||||
| NullOp::AlignOf
|
||||
| NullOp::OffsetOf(..)
|
||||
| NullOp::UbChecks
|
||||
| NullOp::ContractChecks,
|
||||
_,
|
||||
) => {}
|
||||
}
|
||||
|
|
|
@ -545,6 +545,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
|
|||
.offset_of_subfield(self.typing_env(), layout, fields.iter())
|
||||
.bytes(),
|
||||
NullOp::UbChecks => return None,
|
||||
NullOp::ContractChecks => return None,
|
||||
};
|
||||
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
|
||||
let imm = ImmTy::from_uint(val, usize_layout);
|
||||
|
|
|
@ -629,6 +629,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
|
|||
.offset_of_subfield(self.typing_env, op_layout, fields.iter())
|
||||
.bytes(),
|
||||
NullOp::UbChecks => return None,
|
||||
NullOp::ContractChecks => return None,
|
||||
};
|
||||
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
|
||||
}
|
||||
|
|
|
@ -34,6 +34,17 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
|
|||
});
|
||||
terminator.kind = TerminatorKind::Goto { target };
|
||||
}
|
||||
sym::contract_checks => {
|
||||
let target = target.unwrap();
|
||||
block.statements.push(Statement {
|
||||
source_info: terminator.source_info,
|
||||
kind: StatementKind::Assign(Box::new((
|
||||
*destination,
|
||||
Rvalue::NullaryOp(NullOp::ContractChecks, tcx.types.bool),
|
||||
))),
|
||||
});
|
||||
terminator.kind = TerminatorKind::Goto { target };
|
||||
}
|
||||
sym::forget => {
|
||||
let target = target.unwrap();
|
||||
block.statements.push(Statement {
|
||||
|
|
|
@ -457,6 +457,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
|||
NullOp::AlignOf => {}
|
||||
NullOp::OffsetOf(_) => {}
|
||||
NullOp::UbChecks => {}
|
||||
NullOp::ContractChecks => {}
|
||||
},
|
||||
|
||||
Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
|
||||
|
|
|
@ -1379,7 +1379,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
Rvalue::Repeat(_, _)
|
||||
| Rvalue::ThreadLocalRef(_)
|
||||
| Rvalue::RawPtr(_, _)
|
||||
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _)
|
||||
| Rvalue::NullaryOp(
|
||||
NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks | NullOp::ContractChecks,
|
||||
_,
|
||||
)
|
||||
| Rvalue::Discriminant(_) => {}
|
||||
|
||||
Rvalue::WrapUnsafeBinder(op, ty) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ use rustc_ast::{
|
|||
WhereClause, token,
|
||||
};
|
||||
use rustc_errors::{Applicability, PResult};
|
||||
use rustc_span::{Ident, Span, kw};
|
||||
use rustc_span::{Ident, Span, kw, sym};
|
||||
use thin_vec::ThinVec;
|
||||
|
||||
use super::{ForceCollect, Parser, Trailing, UsePreAttrPos};
|
||||
|
@ -297,6 +297,42 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Parses an experimental fn contract
|
||||
/// (`contract_requires(WWW) contract_ensures(ZZZ)`)
|
||||
pub(super) fn parse_contract(
|
||||
&mut self,
|
||||
) -> PResult<'a, Option<rustc_ast::ptr::P<ast::FnContract>>> {
|
||||
let gate = |span| {
|
||||
if self.psess.contract_attribute_spans.contains(span) {
|
||||
// span was generated via a builtin contracts attribute, so gate as end-user visible
|
||||
self.psess.gated_spans.gate(sym::contracts, span);
|
||||
} else {
|
||||
// span was not generated via a builtin contracts attribute, so gate as internal machinery
|
||||
self.psess.gated_spans.gate(sym::contracts_internals, span);
|
||||
}
|
||||
};
|
||||
|
||||
let requires = if self.eat_keyword_noexpect(exp!(ContractRequires).kw) {
|
||||
let precond = self.parse_expr()?;
|
||||
gate(precond.span);
|
||||
Some(precond)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ensures = if self.eat_keyword_noexpect(exp!(ContractEnsures).kw) {
|
||||
let postcond = self.parse_expr()?;
|
||||
gate(postcond.span);
|
||||
Some(postcond)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if requires.is_none() && ensures.is_none() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires, ensures })))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an optional where-clause.
|
||||
///
|
||||
/// ```ignore (only-for-syntax-highlight)
|
||||
|
|
|
@ -213,9 +213,12 @@ impl<'a> Parser<'a> {
|
|||
self.parse_use_item()?
|
||||
} else if self.check_fn_front_matter(check_pub, case) {
|
||||
// FUNCTION ITEM
|
||||
let (ident, sig, generics, body) =
|
||||
let (ident, sig, generics, contract, body) =
|
||||
self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?;
|
||||
(ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body })))
|
||||
(
|
||||
ident,
|
||||
ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, contract, body })),
|
||||
)
|
||||
} else if self.eat_keyword(exp!(Extern)) {
|
||||
if self.eat_keyword(exp!(Crate)) {
|
||||
// EXTERN CRATE
|
||||
|
@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> {
|
|||
sig_lo: Span,
|
||||
vis: &Visibility,
|
||||
case: Case,
|
||||
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<Block>>)> {
|
||||
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<FnContract>>, Option<P<Block>>)> {
|
||||
let fn_span = self.token.span;
|
||||
let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn`
|
||||
let ident = self.parse_ident()?; // `foo`
|
||||
|
@ -2398,6 +2401,8 @@ impl<'a> Parser<'a> {
|
|||
// inside `parse_fn_body()`.
|
||||
let fn_params_end = self.prev_token.span.shrink_to_hi();
|
||||
|
||||
let contract = self.parse_contract()?;
|
||||
|
||||
generics.where_clause = self.parse_where_clause()?; // `where T: Ord`
|
||||
|
||||
// `fn_params_end` is needed only when it's followed by a where clause.
|
||||
|
@ -2409,7 +2414,7 @@ impl<'a> Parser<'a> {
|
|||
let body =
|
||||
self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?;
|
||||
let fn_sig_span = sig_lo.to(sig_hi);
|
||||
Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, body))
|
||||
Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, contract, body))
|
||||
}
|
||||
|
||||
/// Provide diagnostics when function body is not found
|
||||
|
|
|
@ -83,6 +83,8 @@ pub enum TokenType {
|
|||
KwCatch,
|
||||
KwConst,
|
||||
KwContinue,
|
||||
KwContractEnsures,
|
||||
KwContractRequires,
|
||||
KwCrate,
|
||||
KwDefault,
|
||||
KwDyn,
|
||||
|
@ -217,6 +219,8 @@ impl TokenType {
|
|||
KwCatch,
|
||||
KwConst,
|
||||
KwContinue,
|
||||
KwContractEnsures,
|
||||
KwContractRequires,
|
||||
KwCrate,
|
||||
KwDefault,
|
||||
KwDyn,
|
||||
|
@ -289,6 +293,8 @@ impl TokenType {
|
|||
TokenType::KwCatch => Some(kw::Catch),
|
||||
TokenType::KwConst => Some(kw::Const),
|
||||
TokenType::KwContinue => Some(kw::Continue),
|
||||
TokenType::KwContractEnsures => Some(kw::ContractEnsures),
|
||||
TokenType::KwContractRequires => Some(kw::ContractRequires),
|
||||
TokenType::KwCrate => Some(kw::Crate),
|
||||
TokenType::KwDefault => Some(kw::Default),
|
||||
TokenType::KwDyn => Some(kw::Dyn),
|
||||
|
@ -519,6 +525,8 @@ macro_rules! exp {
|
|||
(Catch) => { exp!(@kw, Catch, KwCatch) };
|
||||
(Const) => { exp!(@kw, Const, KwConst) };
|
||||
(Continue) => { exp!(@kw, Continue, KwContinue) };
|
||||
(ContractEnsures) => { exp!(@kw, ContractEnsures, KwContractEnsures) };
|
||||
(ContractRequires) => { exp!(@kw, ContractRequires, KwContractRequires) };
|
||||
(Crate) => { exp!(@kw, Crate, KwCrate) };
|
||||
(Default) => { exp!(@kw, Default, KwDefault) };
|
||||
(Dyn) => { exp!(@kw, Dyn, KwDyn) };
|
||||
|
|
|
@ -174,10 +174,13 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
|
|||
_ctxt,
|
||||
_ident,
|
||||
_vis,
|
||||
Fn { sig: FnSig { header, decl, span: _ }, generics, body, .. },
|
||||
Fn { sig: FnSig { header, decl, span: _ }, generics, contract, body, .. },
|
||||
) if let Some(coroutine_kind) = header.coroutine_kind => {
|
||||
self.visit_fn_header(header);
|
||||
self.visit_generics(generics);
|
||||
if let Some(contract) = contract {
|
||||
self.visit_contract(contract);
|
||||
}
|
||||
|
||||
// For async functions, we need to create their inner defs inside of a
|
||||
// closure to match their desugared representation. Besides that,
|
||||
|
|
|
@ -1019,7 +1019,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
|
|||
// Create a label rib for the function.
|
||||
this.with_label_rib(RibKind::FnOrCoroutine, |this| {
|
||||
match fn_kind {
|
||||
FnKind::Fn(_, _, _, Fn { sig, generics, body, .. }) => {
|
||||
FnKind::Fn(_, _, _, Fn { sig, generics, contract, body, .. }) => {
|
||||
this.visit_generics(generics);
|
||||
|
||||
let declaration = &sig.decl;
|
||||
|
@ -1046,6 +1046,10 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
|
|||
},
|
||||
);
|
||||
|
||||
if let Some(contract) = contract {
|
||||
this.visit_contract(contract);
|
||||
}
|
||||
|
||||
if let Some(body) = body {
|
||||
// Ignore errors in function bodies if this is rustdoc
|
||||
// Be sure not to set this until the function signature has been resolved.
|
||||
|
|
|
@ -119,6 +119,7 @@ pub(crate) fn disallow_cfgs(sess: &Session, user_cfgs: &Cfg) {
|
|||
(sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"),
|
||||
(sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"),
|
||||
(sym::ub_checks, None) => disallow(cfg, "-Z ub-checks"),
|
||||
(sym::contract_checks, None) => disallow(cfg, "-Z contract-checks"),
|
||||
(sym::sanitize, None | Some(_)) => disallow(cfg, "-Z sanitizer"),
|
||||
(
|
||||
sym::sanitizer_cfi_generalize_pointers | sym::sanitizer_cfi_normalize_integers,
|
||||
|
@ -300,6 +301,11 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg {
|
|||
if sess.is_nightly_build() && sess.opts.unstable_opts.emscripten_wasm_eh {
|
||||
ins_none!(sym::emscripten_wasm_eh);
|
||||
}
|
||||
|
||||
if sess.contract_checks() {
|
||||
ins_none!(sym::contract_checks);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -464,6 +470,7 @@ impl CheckCfg {
|
|||
ins!(sym::target_thread_local, no_values);
|
||||
|
||||
ins!(sym::ub_checks, no_values);
|
||||
ins!(sym::contract_checks, no_values);
|
||||
|
||||
ins!(sym::unix, no_values);
|
||||
ins!(sym::windows, no_values);
|
||||
|
|
|
@ -2114,6 +2114,8 @@ options! {
|
|||
"the backend to use"),
|
||||
combine_cgu: bool = (false, parse_bool, [TRACKED],
|
||||
"combine CGUs into a single one"),
|
||||
contract_checks: Option<bool> = (None, parse_opt_bool, [TRACKED],
|
||||
"emit runtime checks for contract pre- and post-conditions (default: no)"),
|
||||
coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED],
|
||||
"control details of coverage instrumentation"),
|
||||
crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],
|
||||
|
|
|
@ -207,6 +207,10 @@ pub struct ParseSess {
|
|||
pub config: Cfg,
|
||||
pub check_config: CheckCfg,
|
||||
pub edition: Edition,
|
||||
/// Places where contract attributes were expanded into unstable AST forms.
|
||||
/// This is used to allowlist those spans (so that we only check them against the feature
|
||||
/// gate for the externally visible interface, and not internal implmentation machinery).
|
||||
pub contract_attribute_spans: AppendOnlyVec<Span>,
|
||||
/// Places where raw identifiers were used. This is used to avoid complaining about idents
|
||||
/// clashing with keywords in new editions.
|
||||
pub raw_identifier_spans: AppendOnlyVec<Span>,
|
||||
|
@ -255,6 +259,7 @@ impl ParseSess {
|
|||
config: Cfg::default(),
|
||||
check_config: CheckCfg::default(),
|
||||
edition: ExpnId::root().expn_data().edition,
|
||||
contract_attribute_spans: Default::default(),
|
||||
raw_identifier_spans: Default::default(),
|
||||
bad_unicode_identifiers: Lock::new(Default::default()),
|
||||
source_map,
|
||||
|
|
|
@ -709,6 +709,10 @@ impl Session {
|
|||
self.opts.unstable_opts.ub_checks.unwrap_or(self.opts.debug_assertions)
|
||||
}
|
||||
|
||||
pub fn contract_checks(&self) -> bool {
|
||||
self.opts.unstable_opts.contract_checks.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn relocation_model(&self) -> RelocModel {
|
||||
self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model)
|
||||
}
|
||||
|
|
|
@ -291,6 +291,7 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
|
|||
indices.iter().map(|idx| idx.stable(tables)).collect(),
|
||||
),
|
||||
UbChecks => stable_mir::mir::NullOp::UbChecks,
|
||||
ContractChecks => stable_mir::mir::NullOp::ContractChecks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1163,6 +1163,8 @@ pub enum DesugaringKind {
|
|||
WhileLoop,
|
||||
/// `async Fn()` bound modifier
|
||||
BoundModifier,
|
||||
/// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond)
|
||||
Contract,
|
||||
}
|
||||
|
||||
impl DesugaringKind {
|
||||
|
@ -1179,6 +1181,7 @@ impl DesugaringKind {
|
|||
DesugaringKind::ForLoop => "`for` loop",
|
||||
DesugaringKind::WhileLoop => "`while` loop",
|
||||
DesugaringKind::BoundModifier => "trait bound modifier",
|
||||
DesugaringKind::Contract => "contract check",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,8 @@ symbols! {
|
|||
MacroRules: "macro_rules",
|
||||
Raw: "raw",
|
||||
Reuse: "reuse",
|
||||
ContractEnsures: "contract_ensures",
|
||||
ContractRequires: "contract_requires",
|
||||
Safe: "safe",
|
||||
Union: "union",
|
||||
Yeet: "yeet",
|
||||
|
@ -569,6 +571,7 @@ symbols! {
|
|||
cfg_attr,
|
||||
cfg_attr_multi,
|
||||
cfg_boolean_literals,
|
||||
cfg_contract_checks,
|
||||
cfg_doctest,
|
||||
cfg_emscripten_wasm_eh,
|
||||
cfg_eval,
|
||||
|
@ -678,6 +681,14 @@ symbols! {
|
|||
const_ty_placeholder: "<const_ty>",
|
||||
constant,
|
||||
constructor,
|
||||
contract_build_check_ensures,
|
||||
contract_check_ensures,
|
||||
contract_check_requires,
|
||||
contract_checks,
|
||||
contracts,
|
||||
contracts_ensures,
|
||||
contracts_internals,
|
||||
contracts_requires,
|
||||
convert_identity,
|
||||
copy,
|
||||
copy_closures,
|
||||
|
|
|
@ -608,7 +608,8 @@ impl Rvalue {
|
|||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
|
||||
Ok(Ty::usize_ty())
|
||||
}
|
||||
Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()),
|
||||
Rvalue::NullaryOp(NullOp::ContractChecks, _)
|
||||
| Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()),
|
||||
Rvalue::Aggregate(ak, ops) => match *ak {
|
||||
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
|
||||
AggregateKind::Tuple => Ok(Ty::new_tuple(
|
||||
|
@ -1007,6 +1008,8 @@ pub enum NullOp {
|
|||
OffsetOf(Vec<(VariantIdx, FieldIdx)>),
|
||||
/// cfg!(ub_checks), but at codegen time
|
||||
UbChecks,
|
||||
/// cfg!(contract_checks), but at codegen time
|
||||
ContractChecks,
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
|
|
21
library/core/src/contracts.rs
Normal file
21
library/core/src/contracts.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
//! Unstable module containing the unstable contracts lang items and attribute macros.
|
||||
#![cfg(not(bootstrap))]
|
||||
|
||||
pub use crate::macros::builtin::{contracts_ensures as ensures, contracts_requires as requires};
|
||||
|
||||
/// Emitted by rustc as a desugaring of `#[ensures(PRED)] fn foo() -> R { ... [return R;] ... }`
|
||||
/// into: `fn foo() { let _check = build_check_ensures(|ret| PRED) ... [return _check(R);] ... }`
|
||||
/// (including the implicit return of the tail expression, if any).
|
||||
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[lang = "contract_build_check_ensures"]
|
||||
#[track_caller]
|
||||
pub fn build_check_ensures<Ret, C>(cond: C) -> impl (Fn(Ret) -> Ret) + Copy
|
||||
where
|
||||
C: for<'a> Fn(&'a Ret) -> bool + Copy + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
move |ret| {
|
||||
crate::intrinsics::contract_check_ensures(&ret, cond);
|
||||
ret
|
||||
}
|
||||
}
|
|
@ -4064,6 +4064,52 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize)
|
|||
// Runtime NOP
|
||||
}
|
||||
|
||||
/// Returns whether we should perform contract-checking at runtime.
|
||||
///
|
||||
/// This is meant to be similar to the ub_checks intrinsic, in terms
|
||||
/// of not prematurely commiting at compile-time to whether contract
|
||||
/// checking is turned on, so that we can specify contracts in libstd
|
||||
/// and let an end user opt into turning them on.
|
||||
#[cfg(not(bootstrap))]
|
||||
#[rustc_const_unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[inline(always)]
|
||||
#[rustc_intrinsic]
|
||||
pub const fn contract_checks() -> bool {
|
||||
// FIXME: should this be `false` or `cfg!(contract_checks)`?
|
||||
|
||||
// cfg!(contract_checks)
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if the pre-condition `cond` has been met.
|
||||
///
|
||||
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
|
||||
/// returns false.
|
||||
#[cfg(not(bootstrap))]
|
||||
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[lang = "contract_check_requires"]
|
||||
#[rustc_intrinsic]
|
||||
pub fn contract_check_requires<C: Fn() -> bool>(cond: C) {
|
||||
if contract_checks() && !cond() {
|
||||
// Emit no unwind panic in case this was a safety requirement.
|
||||
crate::panicking::panic_nounwind("failed requires check");
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the post-condition `cond` has been met.
|
||||
///
|
||||
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
|
||||
/// returns false.
|
||||
#[cfg(not(bootstrap))]
|
||||
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[rustc_intrinsic]
|
||||
pub fn contract_check_ensures<'a, Ret, C: Fn(&'a Ret) -> bool>(ret: &'a Ret, cond: C) {
|
||||
if contract_checks() && !cond(ret) {
|
||||
crate::panicking::panic_nounwind("failed ensures check");
|
||||
}
|
||||
}
|
||||
|
||||
/// The intrinsic will return the size stored in that vtable.
|
||||
///
|
||||
/// # Safety
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
#![feature(bigint_helper_methods)]
|
||||
#![feature(bstr)]
|
||||
#![feature(bstr_internals)]
|
||||
#![feature(closure_track_caller)]
|
||||
#![feature(const_carrying_mul_add)]
|
||||
#![feature(const_eval_select)]
|
||||
#![feature(core_intrinsics)]
|
||||
|
@ -247,6 +248,10 @@ pub mod autodiff {
|
|||
pub use crate::macros::builtin::autodiff;
|
||||
}
|
||||
|
||||
#[cfg(not(bootstrap))]
|
||||
#[unstable(feature = "contracts", issue = "128044")]
|
||||
pub mod contracts;
|
||||
|
||||
#[unstable(feature = "cfg_match", issue = "115585")]
|
||||
pub use crate::macros::cfg_match;
|
||||
|
||||
|
|
|
@ -1777,6 +1777,32 @@ pub(crate) mod builtin {
|
|||
/* compiler built-in */
|
||||
}
|
||||
|
||||
/// Attribute macro applied to a function to give it a post-condition.
|
||||
///
|
||||
/// The attribute carries an argument token-tree which is
|
||||
/// eventually parsed as a unary closure expression that is
|
||||
/// invoked on a reference to the return value.
|
||||
#[cfg(not(bootstrap))]
|
||||
#[unstable(feature = "contracts", issue = "128044")]
|
||||
#[allow_internal_unstable(contracts_internals)]
|
||||
#[rustc_builtin_macro]
|
||||
pub macro contracts_ensures($item:item) {
|
||||
/* compiler built-in */
|
||||
}
|
||||
|
||||
/// Attribute macro applied to a function to give it a precondition.
|
||||
///
|
||||
/// The attribute carries an argument token-tree which is
|
||||
/// eventually parsed as an boolean expression with access to the
|
||||
/// function's formal parameters
|
||||
#[cfg(not(bootstrap))]
|
||||
#[unstable(feature = "contracts", issue = "128044")]
|
||||
#[allow_internal_unstable(contracts_internals)]
|
||||
#[rustc_builtin_macro]
|
||||
pub macro contracts_requires($item:item) {
|
||||
/* compiler built-in */
|
||||
}
|
||||
|
||||
/// Attribute macro applied to a function to register it as a handler for allocation failure.
|
||||
///
|
||||
/// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html).
|
||||
|
|
|
@ -362,18 +362,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
|
|||
defaultness: ld,
|
||||
sig: lf,
|
||||
generics: lg,
|
||||
contract: lc,
|
||||
body: lb,
|
||||
}),
|
||||
Fn(box ast::Fn {
|
||||
defaultness: rd,
|
||||
sig: rf,
|
||||
generics: rg,
|
||||
contract: rc,
|
||||
body: rb,
|
||||
}),
|
||||
) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_fn_sig(lf, rf)
|
||||
&& eq_generics(lg, rg)
|
||||
&& eq_opt_fn_contract(lc, rc)
|
||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||
},
|
||||
(Mod(lu, lmk), Mod(ru, rmk)) => {
|
||||
|
@ -497,18 +500,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
|
|||
defaultness: ld,
|
||||
sig: lf,
|
||||
generics: lg,
|
||||
contract: lc,
|
||||
body: lb,
|
||||
}),
|
||||
Fn(box ast::Fn {
|
||||
defaultness: rd,
|
||||
sig: rf,
|
||||
generics: rg,
|
||||
contract: rc,
|
||||
body: rb,
|
||||
}),
|
||||
) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_fn_sig(lf, rf)
|
||||
&& eq_generics(lg, rg)
|
||||
&& eq_opt_fn_contract(lc, rc)
|
||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||
},
|
||||
(
|
||||
|
@ -559,18 +565,21 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
|
|||
defaultness: ld,
|
||||
sig: lf,
|
||||
generics: lg,
|
||||
contract: lc,
|
||||
body: lb,
|
||||
}),
|
||||
Fn(box ast::Fn {
|
||||
defaultness: rd,
|
||||
sig: rf,
|
||||
generics: rg,
|
||||
contract: rc,
|
||||
body: rb,
|
||||
}),
|
||||
) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_fn_sig(lf, rf)
|
||||
&& eq_generics(lg, rg)
|
||||
&& eq_opt_fn_contract(lc, rc)
|
||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||
},
|
||||
(
|
||||
|
@ -653,6 +662,17 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
|
|||
&& eq_ext(&l.ext, &r.ext)
|
||||
}
|
||||
|
||||
pub fn eq_opt_fn_contract(l: &Option<P<FnContract>>, r: &Option<P<FnContract>>) -> bool {
|
||||
match (l, r) {
|
||||
(Some(l), Some(r)) => {
|
||||
eq_expr_opt(l.requires.as_ref(), r.requires.as_ref())
|
||||
&& eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref())
|
||||
}
|
||||
(None, None) => true,
|
||||
(Some(_), None) | (None, Some(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
|
||||
over(&l.params, &r.params, eq_generic_param)
|
||||
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
|
||||
|
|
|
@ -179,7 +179,7 @@ fn check_rvalue<'tcx>(
|
|||
))
|
||||
}
|
||||
},
|
||||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _)
|
||||
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks, _)
|
||||
| Rvalue::ShallowInitBox(_, _) => Ok(()),
|
||||
Rvalue::UnaryOp(_, operand) => {
|
||||
let ty = operand.ty(body, tcx);
|
||||
|
|
|
@ -1150,6 +1150,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
|
|||
interp_ok(ecx.tcx.sess.ub_checks())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contract_checks(ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool> {
|
||||
interp_ok(ecx.tcx.sess.contract_checks())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn thread_local_static_pointer(
|
||||
ecx: &mut MiriInterpCx<'tcx>,
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `has_foo`
|
|||
LL | #[cfg(has_foo)]
|
||||
| ^^^^^^^
|
||||
|
|
||||
= help: expected names are: `has_bar` and 30 more
|
||||
= help: expected names are: `has_bar` and 31 more
|
||||
= help: consider using a Cargo feature instead
|
||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||
[lints.rust]
|
||||
|
|
|
@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
|
|||
LL | #[cfg(tokio_unstable)]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `docsrs`, `feature`, and `test` and 30 more
|
||||
= help: expected names are: `docsrs`, `feature`, and `test` and 31 more
|
||||
= help: consider using a Cargo feature instead
|
||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||
[lints.rust]
|
||||
|
|
|
@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
|
|||
LL | #[cfg(tokio_unstable)]
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `CONFIG_NVME`, `docsrs`, `feature`, and `test` and 30 more
|
||||
= help: expected names are: `CONFIG_NVME`, `docsrs`, `feature`, and `test` and 31 more
|
||||
= help: consider using a Cargo feature instead
|
||||
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
|
||||
[lints.rust]
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `value`
|
|||
LL | #[cfg(value)]
|
||||
| ^^^^^
|
||||
|
|
||||
= help: expected names are: `bar`, `bee`, `cow`, and `foo` and 30 more
|
||||
= help: expected names are: `bar`, `bee`, `cow`, and `foo` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(value)`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_value`
|
|||
LL | #[cfg(my_value)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `bar` and `foo` and 30 more
|
||||
= help: expected names are: `bar` and `foo` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(my_value)`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
|
|||
LL | #[cfg(unknown_key = "value")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `feature` and 30 more
|
||||
= help: expected names are: `feature` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
|
|||
LL | #[cfg(unknown_key = "value")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `feature` and 30 more
|
||||
= help: expected names are: `feature` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(unknown_key, values("value"))`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||
|
|
|
@ -44,7 +44,7 @@ warning: unexpected `cfg` condition name: `uu`
|
|||
LL | #[cfg_attr(uu, unix)]
|
||||
| ^^
|
||||
|
|
||||
= help: expected names are: `feature` and 30 more
|
||||
= help: expected names are: `feature` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(uu)`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false`
|
|||
LL | #[cfg(r#false)]
|
||||
| ^^^^^^^
|
||||
|
|
||||
= help: expected names are: `async`, `edition2015`, `edition2021`, and `r#true` and 30 more
|
||||
= help: expected names are: `async`, `edition2015`, `edition2021`, and `r#true` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(r#false)`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false`
|
|||
LL | #[cfg(r#false)]
|
||||
| ^^^^^^^
|
||||
|
|
||||
= help: expected names are: `r#async`, `edition2015`, `edition2021`, and `r#true` and 30 more
|
||||
= help: expected names are: `r#async`, `edition2015`, `edition2021`, and `r#true` and 31 more
|
||||
= help: to expect this configuration use `--check-cfg=cfg(r#false)`
|
||||
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg`
|
|||
LL | cfg_macro::my_lib_macro!();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `feature` and 30 more
|
||||
= help: expected names are: `feature` and 31 more
|
||||
= note: using a cfg inside a macro will use the cfgs from the destination crate and not the ones from the defining crate
|
||||
= help: try referring to `cfg_macro::my_lib_macro` crate for guidance on how handle this unexpected cfg
|
||||
= help: the macro `cfg_macro::my_lib_macro` may come from an old version of the `cfg_macro` crate, try updating your dependency with `cargo update -p cfg_macro`
|
||||
|
|
|
@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg`
|
|||
LL | cfg_macro::my_lib_macro!();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `feature` and 30 more
|
||||
= help: expected names are: `feature` and 31 more
|
||||
= note: using a cfg inside a macro will use the cfgs from the destination crate and not the ones from the defining crate
|
||||
= help: try referring to `cfg_macro::my_lib_macro` crate for guidance on how handle this unexpected cfg
|
||||
= help: to expect this configuration use `--check-cfg=cfg(my_lib_cfg)`
|
||||
|
|
|
@ -5,6 +5,7 @@ LL | #[cfg(list_all_well_known_cfgs)]
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: expected names are: `clippy`
|
||||
`contract_checks`
|
||||
`debug_assertions`
|
||||
`doc`
|
||||
`doctest`
|
||||
|
|
28
tests/ui/contracts/contract-annotation-limitations.rs
Normal file
28
tests/ui/contracts/contract-annotation-limitations.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
//! Test for some of the existing limitations and the current error messages.
|
||||
//! Some of these limitations may be removed in the future.
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
#![allow(dead_code)]
|
||||
|
||||
/// Represent a 5-star system.
|
||||
struct Stars(u8);
|
||||
|
||||
impl Stars {
|
||||
fn is_valid(&self) -> bool {
|
||||
self.0 <= 5
|
||||
}
|
||||
}
|
||||
|
||||
trait ParseStars {
|
||||
#[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||
//~^ ERROR contract annotations is only supported in functions with bodies
|
||||
fn parse_string(input: String) -> Option<Stars>;
|
||||
|
||||
#[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||
//~^ ERROR contract annotations is only supported in functions with bodies
|
||||
fn parse<T>(input: T) -> Option<Stars> where T: for<'a> Into<&'a str>;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
}
|
23
tests/ui/contracts/contract-annotation-limitations.stderr
Normal file
23
tests/ui/contracts/contract-annotation-limitations.stderr
Normal file
|
@ -0,0 +1,23 @@
|
|||
error: contract annotations is only supported in functions with bodies
|
||||
--> $DIR/contract-annotation-limitations.rs:18:5
|
||||
|
|
||||
LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: contract annotations is only supported in functions with bodies
|
||||
--> $DIR/contract-annotation-limitations.rs:22:5
|
||||
|
|
||||
LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-annotation-limitations.rs:4:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
error: aborting due to 2 previous errors; 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-generics.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-generics.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-generics.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-generics.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
71
tests/ui/contracts/contract-attributes-generics.rs
Normal file
71
tests/ui/contracts/contract-attributes-generics.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
//! Test that contracts can be applied to generic functions.
|
||||
|
||||
//@ revisions: unchk_pass chk_pass chk_fail_pre chk_fail_post chk_const_fail
|
||||
//
|
||||
//@ [unchk_pass] run-pass
|
||||
//@ [chk_pass] run-pass
|
||||
//
|
||||
//@ [chk_fail_pre] run-fail
|
||||
//@ [chk_fail_post] run-fail
|
||||
//@ [chk_const_fail] run-fail
|
||||
//
|
||||
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||
//
|
||||
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_const_fail] compile-flags: -Zcontract-checks=yes
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
|
||||
use std::ops::Sub;
|
||||
|
||||
/// Dummy fn contract that precondition fails for val < 0, and post-condition fail for val == 1
|
||||
#[core::contracts::requires(val > 0u8.into())]
|
||||
#[core::contracts::ensures(|ret| *ret > 0u8.into())]
|
||||
fn decrement<T>(val: T) -> T
|
||||
where T: PartialOrd + Sub<Output=T> + From<u8>
|
||||
{
|
||||
val - 1u8.into()
|
||||
}
|
||||
|
||||
/// Create a structure that takes a constant parameter.
|
||||
#[allow(dead_code)]
|
||||
struct Capped<const MAX: usize>(usize);
|
||||
|
||||
/// Now declare a function to create stars which shouldn't exceed 5 stars.
|
||||
// Add redundant braces to ensure the built-in macro can handle this syntax.
|
||||
#[allow(unused_braces)]
|
||||
#[core::contracts::requires(num <= 5)]
|
||||
unsafe fn stars_unchecked(num: usize) -> Capped<{ 5 }> {
|
||||
Capped(num)
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
check_decrement();
|
||||
check_stars();
|
||||
}
|
||||
|
||||
fn check_stars() {
|
||||
// This should always pass.
|
||||
let _ = unsafe { stars_unchecked(3) };
|
||||
|
||||
// This violates the contract.
|
||||
#[cfg(any(unchk_pass, chk_const_fail))]
|
||||
let _ = unsafe { stars_unchecked(10) };
|
||||
}
|
||||
|
||||
fn check_decrement() {
|
||||
// This should always pass
|
||||
assert_eq!(decrement(10u8), 9u8);
|
||||
|
||||
// This should fail requires but pass with no contract check.
|
||||
#[cfg(any(unchk_pass, chk_fail_pre))]
|
||||
assert_eq!(decrement(-2i128), -3i128);
|
||||
|
||||
// This should fail ensures but pass with no contract check.
|
||||
#[cfg(any(unchk_pass, chk_fail_post))]
|
||||
assert_eq!(decrement(1i32), 0i32);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-generics.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-nest.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-nest.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
11
tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
Normal file
11
tests/ui/contracts/contract-attributes-nest.chk_pass.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-nest.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
45
tests/ui/contracts/contract-attributes-nest.rs
Normal file
45
tests/ui/contracts/contract-attributes-nest.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
|
||||
//
|
||||
//@ [unchk_pass] run-pass
|
||||
//@ [unchk_fail_pre] run-pass
|
||||
//@ [unchk_fail_post] run-pass
|
||||
//@ [chk_pass] run-pass
|
||||
//
|
||||
//@ [chk_fail_pre] run-fail
|
||||
//@ [chk_fail_post] run-fail
|
||||
//
|
||||
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no
|
||||
//
|
||||
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
|
||||
#[core::contracts::requires(x.baz > 0)]
|
||||
#[core::contracts::ensures(|ret| *ret > 100)]
|
||||
fn nest(x: Baz) -> i32
|
||||
{
|
||||
loop {
|
||||
return x.baz + 50;
|
||||
}
|
||||
}
|
||||
|
||||
struct Baz { baz: i32 }
|
||||
|
||||
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
|
||||
|
||||
fn main() {
|
||||
assert_eq!(nest(BAZ_PASS_PRE_POST), 150);
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
nest(BAZ_FAIL_PRE);
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
nest(BAZ_FAIL_POST);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-nest.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-nest.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-nest.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-tail.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-tail.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
11
tests/ui/contracts/contract-attributes-tail.chk_pass.stderr
Normal file
11
tests/ui/contracts/contract-attributes-tail.chk_pass.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-tail.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
43
tests/ui/contracts/contract-attributes-tail.rs
Normal file
43
tests/ui/contracts/contract-attributes-tail.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
|
||||
//
|
||||
//@ [unchk_pass] run-pass
|
||||
//@ [unchk_fail_pre] run-pass
|
||||
//@ [unchk_fail_post] run-pass
|
||||
//@ [chk_pass] run-pass
|
||||
//
|
||||
//@ [chk_fail_pre] run-fail
|
||||
//@ [chk_fail_post] run-fail
|
||||
//
|
||||
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_post] compile-flags: -Zcontract-checks=no
|
||||
//
|
||||
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
|
||||
#[core::contracts::requires(x.baz > 0)]
|
||||
#[core::contracts::ensures(|ret| *ret > 100)]
|
||||
fn tail(x: Baz) -> i32
|
||||
{
|
||||
x.baz + 50
|
||||
}
|
||||
|
||||
struct Baz { baz: i32 }
|
||||
|
||||
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
|
||||
|
||||
fn main() {
|
||||
assert_eq!(tail(BAZ_PASS_PRE_POST), 150);
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
tail(BAZ_FAIL_PRE);
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
tail(BAZ_FAIL_POST);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-tail.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-tail.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-attributes-tail.rs:19:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
26
tests/ui/contracts/contract-captures-via-closure-copy.rs
Normal file
26
tests/ui/contracts/contract-captures-via-closure-copy.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
//@ run-fail
|
||||
//@ compile-flags: -Zcontract-checks=yes
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
|
||||
struct Baz {
|
||||
baz: i32
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[core::contracts::requires(x.baz > 0)]
|
||||
#[core::contracts::ensures({let old = x.baz; move |ret:&Baz| ret.baz == old*2 })]
|
||||
// Relevant thing is this: ^^^^^^^^^^^^^^^
|
||||
// because we are capturing state that is Copy
|
||||
fn doubler(x: Baz) -> Baz {
|
||||
Baz { baz: x.baz + 10 }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(doubler(Baz { baz: 10 }).baz, 20);
|
||||
assert_eq!(doubler(Baz { baz: 100 }).baz, 200);
|
||||
// This is a *run-fail* test because it is still exercising the
|
||||
// contract machinery, specifically because this second invocation
|
||||
// of `doubler` shows how the code does not meet its contract.
|
||||
}
|
11
tests/ui/contracts/contract-captures-via-closure-copy.stderr
Normal file
11
tests/ui/contracts/contract-captures-via-closure-copy.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-captures-via-closure-copy.rs:4:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
23
tests/ui/contracts/contract-captures-via-closure-noncopy.rs
Normal file
23
tests/ui/contracts/contract-captures-via-closure-noncopy.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
//@ compile-flags: -Zcontract-checks=yes
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
|
||||
struct Baz {
|
||||
baz: i32
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[core::contracts::requires(x.baz > 0)]
|
||||
#[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })]
|
||||
// Relevant thing is this: ^^^^^^^^^^^
|
||||
// because we are capturing state that is non-Copy.
|
||||
//~^^^ ERROR trait bound `Baz: std::marker::Copy` is not satisfied
|
||||
fn doubler(x: Baz) -> Baz {
|
||||
Baz { baz: x.baz + 10 }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
assert_eq!(doubler(Baz { baz: 10 }).baz, 20);
|
||||
assert_eq!(doubler(Baz { baz: 100 }).baz, 200);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contract-captures-via-closure-noncopy.rs:3:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
error[E0277]: the trait bound `Baz: std::marker::Copy` is not satisfied in `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`
|
||||
--> $DIR/contract-captures-via-closure-noncopy.rs:12:1
|
||||
|
|
||||
LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------------------------------------^^^^
|
||||
| | |
|
||||
| | within this `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`
|
||||
| | this tail expression is of type `{closure@contract-captures-via-closure-noncopy.rs:12:42}`
|
||||
| unsatisfied trait bound
|
||||
|
|
||||
= help: within `{closure@$DIR/contract-captures-via-closure-noncopy.rs:12:42: 12:57}`, the trait `std::marker::Copy` is not implemented for `Baz`
|
||||
note: required because it's used within this closure
|
||||
--> $DIR/contract-captures-via-closure-noncopy.rs:12:42
|
||||
|
|
||||
LL | #[core::contracts::ensures({let old = x; move |ret:&Baz| ret.baz == old.baz*2 })]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
note: required by a bound in `build_check_ensures`
|
||||
--> $SRC_DIR/core/src/contracts.rs:LL:COL
|
||||
help: consider annotating `Baz` with `#[derive(Copy)]`
|
||||
|
|
||||
LL + #[derive(Copy)]
|
||||
LL | struct Baz {
|
||||
|
|
||||
|
||||
error: aborting due to 1 previous error; 1 warning emitted
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contracts-ensures-early-fn-exit.rs:16:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contracts-ensures-early-fn-exit.rs:16:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contracts-ensures-early-fn-exit.rs:16:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contracts-ensures-early-fn-exit.rs:16:12
|
||||
|
|
||||
LL | #![feature(contracts)]
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
= note: `#[warn(incomplete_features)]` on by default
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
49
tests/ui/contracts/contracts-ensures-early-fn-exit.rs
Normal file
49
tests/ui/contracts/contracts-ensures-early-fn-exit.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
//@ revisions: unchk_pass chk_pass chk_fail_try chk_fail_ret chk_fail_yeet
|
||||
//
|
||||
//@ [unchk_pass] run-pass
|
||||
//@ [chk_pass] run-pass
|
||||
//@ [chk_fail_try] run-fail
|
||||
//@ [chk_fail_ret] run-fail
|
||||
//@ [chk_fail_yeet] run-fail
|
||||
//
|
||||
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_try] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_ret] compile-flags: -Zcontract-checks=yes
|
||||
//@ [chk_fail_yeet] compile-flags: -Zcontract-checks=yes
|
||||
//! This test ensures that ensures clauses are checked for different return points of a function.
|
||||
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
#![feature(yeet_expr)]
|
||||
|
||||
/// This ensures will fail in different return points depending on the input.
|
||||
#[core::contracts::ensures(|ret: &Option<u32>| ret.is_some())]
|
||||
fn try_sum(x: u32, y: u32, z: u32) -> Option<u32> {
|
||||
// Use Yeet to return early.
|
||||
if x == u32::MAX && (y > 0 || z > 0) { do yeet }
|
||||
|
||||
// Use `?` to early return.
|
||||
let partial = x.checked_add(y)?;
|
||||
|
||||
// Explicitly use `return` clause.
|
||||
if u32::MAX - partial < z {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(partial + z)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// This should always succeed
|
||||
assert_eq!(try_sum(0, 1, 2), Some(3));
|
||||
|
||||
#[cfg(any(unchk_pass, chk_fail_yeet))]
|
||||
assert_eq!(try_sum(u32::MAX, 1, 1), None);
|
||||
|
||||
#[cfg(any(unchk_pass, chk_fail_try))]
|
||||
assert_eq!(try_sum(u32::MAX - 10, 12, 0), None);
|
||||
|
||||
#[cfg(any(unchk_pass, chk_fail_ret))]
|
||||
assert_eq!(try_sum(u32::MAX - 10, 2, 100), None);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue