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:
León Orell Valerian Liehr 2025-02-05 05:03:01 +01:00 committed by GitHub
commit d81701b610
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
123 changed files with 1869 additions and 39 deletions

View file

@ -3348,11 +3348,18 @@ pub struct Impl {
pub items: ThinVec<P<AssocItem>>, 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)] #[derive(Clone, Encodable, Decodable, Debug)]
pub struct Fn { pub struct Fn {
pub defaultness: Defaultness, pub defaultness: Defaultness,
pub generics: Generics, pub generics: Generics,
pub sig: FnSig, pub sig: FnSig,
pub contract: Option<P<FnContract>>,
pub body: Option<P<Block>>, pub body: Option<P<Block>>,
} }
@ -3650,7 +3657,7 @@ mod size_asserts {
static_assert_size!(Block, 32); static_assert_size!(Block, 32);
static_assert_size!(Expr, 72); static_assert_size!(Expr, 72);
static_assert_size!(ExprKind, 40); static_assert_size!(ExprKind, 40);
static_assert_size!(Fn, 160); static_assert_size!(Fn, 168);
static_assert_size!(ForeignItem, 88); static_assert_size!(ForeignItem, 88);
static_assert_size!(ForeignItemKind, 16); static_assert_size!(ForeignItemKind, 16);
static_assert_size!(GenericArg, 24); static_assert_size!(GenericArg, 24);

View file

@ -143,6 +143,10 @@ pub trait MutVisitor: Sized {
walk_flat_map_assoc_item(self, i, ctxt) 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>) { fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
walk_fn_decl(self, d); walk_fn_decl(self, d);
} }
@ -958,13 +962,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
_ctxt, _ctxt,
_ident, _ident,
_vis, _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. // Identifier and visibility are visited as a part of the item.
visit_defaultness(vis, defaultness); visit_defaultness(vis, defaultness);
vis.visit_fn_header(header); vis.visit_fn_header(header);
vis.visit_generics(generics); vis.visit_generics(generics);
vis.visit_fn_decl(decl); vis.visit_fn_decl(decl);
if let Some(contract) = contract {
vis.visit_contract(contract);
}
if let Some(body) = body { if let Some(body) = body {
vis.visit_block(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>) { fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
let FnDecl { inputs, output } = decl.deref_mut(); let FnDecl { inputs, output } = decl.deref_mut();
inputs.flat_map_in_place(|param| vis.flat_map_param(param)); inputs.flat_map_in_place(|param| vis.flat_map_param(param));

View file

@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized {
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result { fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
walk_closure_binder(self, b) 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 { fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
walk_where_predicate(self, p) walk_where_predicate(self, p)
} }
@ -800,6 +803,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
V::Result::output() 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>>( pub fn walk_where_predicate<'a, V: Visitor<'a>>(
visitor: &mut V, visitor: &mut V,
predicate: &'a WherePredicate, predicate: &'a WherePredicate,
@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
_ctxt, _ctxt,
_ident, _ident,
_vis, _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. // Identifier and visibility are visited as a part of the item.
try_visit!(visitor.visit_fn_header(header)); try_visit!(visitor.visit_fn_header(header));
try_visit!(visitor.visit_generics(generics)); try_visit!(visitor.visit_generics(generics));
try_visit!(visitor.visit_fn_decl(decl)); try_visit!(visitor.visit_fn_decl(decl));
visit_opt!(visitor, visit_contract, contract);
visit_opt!(visitor, visit_block, body); visit_opt!(visitor, visit_block, body);
} }
FnKind::Closure(binder, coroutine_kind, decl, body) => { FnKind::Closure(binder, coroutine_kind, decl, body) => {

View file

@ -311,8 +311,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label)) hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
} }
ExprKind::Ret(e) => { ExprKind::Ret(e) => {
let e = e.as_ref().map(|x| self.lower_expr(x)); let expr = e.as_ref().map(|x| self.lower_expr(x));
hir::ExprKind::Ret(e) self.checked_return(expr)
} }
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()), ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
ExprKind::Become(sub_expr) => { 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 { pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {
self.with_new_scopes(c.value.span, |this| { self.with_new_scopes(c.value.span, |this| {
let def_id = this.local_def_id(c.id); let def_id = this.local_def_id(c.id);
@ -1991,7 +2017,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
), ),
)) ))
} else { } 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); self.lower_attrs(ret_expr.hir_id, &attrs);
@ -2040,7 +2067,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
let target_id = Ok(catch_id); let target_id = Ok(catch_id);
hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr)) hir::ExprKind::Break(hir::Destination { label: None, target_id }, Some(from_yeet_expr))
} else { } else {
hir::ExprKind::Ret(Some(from_yeet_expr)) self.checked_return(Some(from_yeet_expr))
} }
} }

View file

@ -207,9 +207,40 @@ impl<'hir> LoweringContext<'_, 'hir> {
sig: FnSig { decl, header, span: fn_sig_span }, sig: FnSig { decl, header, span: fn_sig_span },
generics, generics,
body, body,
contract,
.. ..
}) => { }) => {
self.with_new_scopes(*fn_sig_span, |this| { 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 // Note: we don't need to change the return type from `T` to
// `impl Future<Output = T>` here because lower_body // `impl Future<Output = T>` here because lower_body
// only cares about the input argument patterns in the function // 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>, body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
) -> hir::BodyId { ) -> hir::BodyId {
self.lower_body(|this| { self.lower_body(|this| {
( let params =
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))), this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
body(this), 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))
}) })
} }

View file

@ -86,6 +86,19 @@ mod path;
rustc_fluent_macro::fluent_messages! { "../messages.ftl" } 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> { struct LoweringContext<'a, 'hir> {
tcx: TyCtxt<'hir>, tcx: TyCtxt<'hir>,
resolver: &'a mut ResolverAstLowering, resolver: &'a mut ResolverAstLowering,
@ -100,6 +113,8 @@ struct LoweringContext<'a, 'hir> {
/// Collect items that were created by lowering the current owner. /// Collect items that were created by lowering the current owner.
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>, children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,
contract: Option<FnContractLoweringInfo<'hir>>,
coroutine_kind: Option<hir::CoroutineKind>, coroutine_kind: Option<hir::CoroutineKind>,
/// When inside an `async` context, this is the `HirId` of the /// When inside an `async` context, this is the `HirId` of the
@ -148,6 +163,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
bodies: Vec::new(), bodies: Vec::new(),
attrs: SortedMap::default(), attrs: SortedMap::default(),
children: Vec::default(), children: Vec::default(),
contract: None,
current_hir_id_owner: hir::CRATE_OWNER_ID, current_hir_id_owner: hir::CRATE_OWNER_ID,
item_local_id_counter: hir::ItemLocalId::ZERO, item_local_id_counter: hir::ItemLocalId::ZERO,
ident_and_label_to_local_id: Default::default(), 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; let was_in_loop_condition = self.is_in_loop_condition;
self.is_in_loop_condition = false; self.is_in_loop_condition = false;
let old_contract = self.contract.take();
let catch_scope = self.catch_scope.take(); let catch_scope = self.catch_scope.take();
let loop_scope = self.loop_scope.take(); let loop_scope = self.loop_scope.take();
let ret = f(self); let ret = f(self);
self.catch_scope = catch_scope; self.catch_scope = catch_scope;
self.loop_scope = loop_scope; self.loop_scope = loop_scope;
self.contract = old_contract;
self.is_in_loop_condition = was_in_loop_condition; self.is_in_loop_condition = was_in_loop_condition;
self.current_item = current_item; self.current_item = current_item;

View file

@ -917,7 +917,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
walk_list!(self, visit_attribute, &item.attrs); walk_list!(self, visit_attribute, &item.attrs);
return; // Avoid visiting again. 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); self.check_defaultness(item.span, *defaultness);
let is_intrinsic = let is_intrinsic =

View file

@ -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!(pin_ergonomics, "pinned reference syntax is experimental");
gate_all!(unsafe_fields, "`unsafe` fields are experimental"); gate_all!(unsafe_fields, "`unsafe` fields are experimental");
gate_all!(unsafe_binders, "unsafe binder types 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 !visitor.features.never_patterns() {
if let Some(spans) = spans.get(&sym::never_patterns) { if let Some(spans) = spans.get(&sym::never_patterns) {

View file

@ -650,13 +650,17 @@ impl<'a> State<'a> {
attrs: &[ast::Attribute], attrs: &[ast::Attribute],
func: &ast::Fn, func: &ast::Fn,
) { ) {
let ast::Fn { defaultness, generics, sig, body } = func; let ast::Fn { defaultness, generics, sig, contract, body } = func;
if body.is_some() { if body.is_some() {
self.head(""); self.head("");
} }
self.print_visibility(vis); self.print_visibility(vis);
self.print_defaultness(*defaultness); self.print_defaultness(*defaultness);
self.print_fn(&sig.decl, sig.header, Some(name), generics); 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 { if let Some(body) = body {
self.nbsp(); self.nbsp();
self.print_block_with_attrs(body, attrs); 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( pub(crate) fn print_fn(
&mut self, &mut self,
decl: &ast::FnDecl, decl: &ast::FnDecl,

View file

@ -1653,6 +1653,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
ConstraintCategory::SizedBound, ConstraintCategory::SizedBound,
); );
} }
&Rvalue::NullaryOp(NullOp::ContractChecks, _) => {}
&Rvalue::NullaryOp(NullOp::UbChecks, _) => {} &Rvalue::NullaryOp(NullOp::UbChecks, _) => {}
Rvalue::ShallowInitBox(operand, ty) => { Rvalue::ShallowInitBox(operand, ty) => {

View file

@ -85,6 +85,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span
defaultness: ast::Defaultness::Final, defaultness: ast::Defaultness::Final,
sig, sig,
generics: Generics::default(), generics: Generics::default(),
contract: None,
body, body,
})); }));

View 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(())
})
}

View file

@ -1034,6 +1034,7 @@ impl<'a> MethodDef<'a> {
defaultness, defaultness,
sig, sig,
generics: fn_generics, generics: fn_generics,
contract: None,
body: Some(body_block), body: Some(body_block),
})), })),
tokens: None, tokens: None,

View file

@ -81,6 +81,7 @@ impl AllocFnFactory<'_, '_> {
defaultness: ast::Defaultness::Final, defaultness: ast::Defaultness::Final,
sig, sig,
generics: Generics::default(), generics: Generics::default(),
contract: None,
body, body,
})); }));
let item = self.cx.item( let item = self.cx.item(

View file

@ -55,6 +55,7 @@ mod trace_macros;
pub mod asm; pub mod asm;
pub mod cmdline_attrs; pub mod cmdline_attrs;
pub mod contracts;
pub mod proc_macro_harness; pub mod proc_macro_harness;
pub mod standard_library_imports; pub mod standard_library_imports;
pub mod test_harness; 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); let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client }))); 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);
} }

View file

@ -344,6 +344,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
defaultness, defaultness,
sig, sig,
generics: ast::Generics::default(), generics: ast::Generics::default(),
contract: None,
body: Some(main_body), body: Some(main_body),
})); }));

View file

@ -868,7 +868,16 @@ fn codegen_stmt<'tcx>(
NullOp::UbChecks => { NullOp::UbChecks => {
let val = fx.tcx.sess.ub_checks(); let val = fx.tcx.sess.ub_checks();
let val = CValue::by_val( 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), fx.layout_of(fx.tcx.types.bool),
); );
lval.write_cvalue(fx, val); lval.write_cvalue(fx, val);

View file

@ -741,6 +741,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let val = bx.tcx().sess.ub_checks(); let val = bx.tcx().sess.ub_checks();
bx.cx().const_bool(val) 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(); let tcx = self.cx.tcx();
OperandRef { OperandRef {

View file

@ -675,7 +675,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Rvalue::Cast(_, _, _) => {} Rvalue::Cast(_, _, _) => {}
Rvalue::NullaryOp( Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, NullOp::SizeOf
| NullOp::AlignOf
| NullOp::OffsetOf(_)
| NullOp::UbChecks
| NullOp::ContractChecks,
_, _,
) => {} ) => {}
Rvalue::ShallowInitBox(_, _) => {} Rvalue::ShallowInitBox(_, _) => {}

View file

@ -293,6 +293,9 @@ pub trait Machine<'tcx>: Sized {
/// Determines the result of a `NullaryOp::UbChecks` invocation. /// Determines the result of a `NullaryOp::UbChecks` invocation.
fn ub_checks(_ecx: &InterpCx<'tcx, Self>) -> InterpResult<'tcx, bool>; 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. /// Called when the interpreter encounters a `StatementKind::ConstEvalCounter` instruction.
/// You can use this to detect long or endlessly running programs. /// You can use this to detect long or endlessly running programs.
#[inline] #[inline]
@ -679,6 +682,13 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
interp_ok(true) 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)] #[inline(always)]
fn adjust_global_allocation<'b>( fn adjust_global_allocation<'b>(
_ecx: &InterpCx<$tcx, Self>, _ecx: &InterpCx<$tcx, Self>,

View file

@ -537,6 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
ImmTy::from_uint(val, usize_layout()) ImmTy::from_uint(val, usize_layout())
} }
UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx), UbChecks => ImmTy::from_bool(M::ub_checks(self)?, *self.tcx),
ContractChecks => ImmTy::from_bool(M::contract_checks(self)?, *self.tcx),
}) })
} }
} }

View file

@ -19,6 +19,7 @@ const GATED_CFGS: &[GatedCfg] = &[
// (name in cfg, feature, function to check if the feature is enabled) // (name in cfg, feature, function to check if the feature is enabled)
(sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks), (sym::overflow_checks, sym::cfg_overflow_checks, Features::cfg_overflow_checks),
(sym::ub_checks, sym::cfg_ub_checks, Features::cfg_ub_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_thread_local, sym::cfg_target_thread_local, Features::cfg_target_thread_local),
( (
sym::target_has_atomic_equal_alignment, sym::target_has_atomic_equal_alignment,

View file

@ -403,6 +403,8 @@ declare_features! (
(unstable, c_variadic, "1.34.0", Some(44930)), (unstable, c_variadic, "1.34.0", Some(44930)),
/// Allows the use of `#[cfg(<true/false>)]`. /// Allows the use of `#[cfg(<true/false>)]`.
(unstable, cfg_boolean_literals, "1.83.0", Some(131204)), (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. /// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour.
(unstable, cfg_overflow_checks, "1.71.0", Some(111466)), (unstable, cfg_overflow_checks, "1.71.0", Some(111466)),
/// Provides the relocation model information as cfg entry /// Provides the relocation model information as cfg entry
@ -445,6 +447,10 @@ declare_features! (
(unstable, const_trait_impl, "1.42.0", Some(67792)), (unstable, const_trait_impl, "1.42.0", Some(67792)),
/// Allows the `?` operator in const contexts. /// Allows the `?` operator in const contexts.
(unstable, const_try, "1.56.0", Some(74935)), (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. /// Allows coroutines to be cloned.
(unstable, coroutine_clone, "1.65.0", Some(95360)), (unstable, coroutine_clone, "1.65.0", Some(95360)),
/// Allows defining coroutines. /// Allows defining coroutines.

View file

@ -2654,6 +2654,8 @@ pub enum LocalSource {
/// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression. /// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression.
/// The span is that of the `=` sign. /// The span is that of the `=` sign.
AssignDesugar(Span), 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 _ { .. }`. /// Hints at the original code for a `match _ { .. }`.

View file

@ -423,6 +423,10 @@ language_item_table! {
String, sym::String, string, Target::Struct, GenericRequirement::None; String, sym::String, string, Target::Struct, GenericRequirement::None;
CStr, sym::CStr, c_str, 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 { pub enum GenericRequirement {

View file

@ -132,6 +132,9 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -
| sym::aggregate_raw_ptr | sym::aggregate_raw_ptr
| sym::ptr_metadata | sym::ptr_metadata
| sym::ub_checks | sym::ub_checks
| sym::contract_checks
| sym::contract_check_requires
| sym::contract_check_ensures
| sym::fadd_algebraic | sym::fadd_algebraic
| sym::fsub_algebraic | sym::fsub_algebraic
| sym::fmul_algebraic | sym::fmul_algebraic
@ -219,6 +222,16 @@ pub fn check_intrinsic_type(
} }
}; };
(n_tps, 0, 0, inputs, output, hir::Safety::Unsafe) (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 { } else {
let safety = intrinsic_operation_unsafety(tcx, intrinsic_id); let safety = intrinsic_operation_unsafety(tcx, intrinsic_id);
let (n_tps, n_cts, inputs, output) = match intrinsic_name { 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))), 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_eq
| sym::simd_ne | sym::simd_ne
| sym::simd_lt | sym::simd_lt

View file

@ -269,6 +269,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// diverging expression (e.g. it arose from desugaring of `try { return }`), // diverging expression (e.g. it arose from desugaring of `try { return }`),
// we skip issuing a warning because it is autogenerated code. // we skip issuing a warning because it is autogenerated code.
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {} 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::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
ExprKind::MethodCall(segment, ..) => { ExprKind::MethodCall(segment, ..) => {
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call") self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")

View file

@ -1104,6 +1104,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
NullOp::AlignOf => write!(fmt, "AlignOf({t})"), NullOp::AlignOf => write!(fmt, "AlignOf({t})"),
NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"),
NullOp::UbChecks => write!(fmt, "UbChecks()"), NullOp::UbChecks => write!(fmt, "UbChecks()"),
NullOp::ContractChecks => write!(fmt, "ContractChecks()"),
} }
} }
ThreadLocalRef(did) => ty::tls::with(|tcx| { ThreadLocalRef(did) => ty::tls::with(|tcx| {

View file

@ -1591,6 +1591,9 @@ pub enum NullOp<'tcx> {
/// Returns whether we should perform some UB-checking at runtime. /// Returns whether we should perform some UB-checking at runtime.
/// See the `ub_checks` intrinsic docs for details. /// See the `ub_checks` intrinsic docs for details.
UbChecks, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]

View file

@ -230,7 +230,8 @@ impl<'tcx> Rvalue<'tcx> {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
tcx.types.usize 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 { Rvalue::Aggregate(ref ak, ref ops) => match **ak {
AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64), AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64),
AggregateKind::Tuple => { AggregateKind::Tuple => {

View file

@ -417,7 +417,11 @@ impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
| Rvalue::Discriminant(..) | Rvalue::Discriminant(..)
| Rvalue::Len(..) | Rvalue::Len(..)
| Rvalue::NullaryOp( | Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks, NullOp::SizeOf
| NullOp::AlignOf
| NullOp::OffsetOf(..)
| NullOp::UbChecks
| NullOp::ContractChecks,
_, _,
) => {} ) => {}
} }

View file

@ -545,6 +545,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
.offset_of_subfield(self.typing_env(), layout, fields.iter()) .offset_of_subfield(self.typing_env(), layout, fields.iter())
.bytes(), .bytes(),
NullOp::UbChecks => return None, NullOp::UbChecks => return None,
NullOp::ContractChecks => return None,
}; };
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap(); let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
let imm = ImmTy::from_uint(val, usize_layout); let imm = ImmTy::from_uint(val, usize_layout);

View file

@ -629,6 +629,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
.offset_of_subfield(self.typing_env, op_layout, fields.iter()) .offset_of_subfield(self.typing_env, op_layout, fields.iter())
.bytes(), .bytes(),
NullOp::UbChecks => return None, NullOp::UbChecks => return None,
NullOp::ContractChecks => return None,
}; };
ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into() ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into()
} }

View file

@ -34,6 +34,17 @@ impl<'tcx> crate::MirPass<'tcx> for LowerIntrinsics {
}); });
terminator.kind = TerminatorKind::Goto { target }; 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 => { sym::forget => {
let target = target.unwrap(); let target = target.unwrap();
block.statements.push(Statement { block.statements.push(Statement {

View file

@ -457,6 +457,7 @@ impl<'tcx> Validator<'_, 'tcx> {
NullOp::AlignOf => {} NullOp::AlignOf => {}
NullOp::OffsetOf(_) => {} NullOp::OffsetOf(_) => {}
NullOp::UbChecks => {} NullOp::UbChecks => {}
NullOp::ContractChecks => {}
}, },
Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable), Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),

View file

@ -1379,7 +1379,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
Rvalue::Repeat(_, _) Rvalue::Repeat(_, _)
| Rvalue::ThreadLocalRef(_) | Rvalue::ThreadLocalRef(_)
| Rvalue::RawPtr(_, _) | Rvalue::RawPtr(_, _)
| Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _) | Rvalue::NullaryOp(
NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks | NullOp::ContractChecks,
_,
)
| Rvalue::Discriminant(_) => {} | Rvalue::Discriminant(_) => {}
Rvalue::WrapUnsafeBinder(op, ty) => { Rvalue::WrapUnsafeBinder(op, ty) => {

View file

@ -4,7 +4,7 @@ use rustc_ast::{
WhereClause, token, WhereClause, token,
}; };
use rustc_errors::{Applicability, PResult}; use rustc_errors::{Applicability, PResult};
use rustc_span::{Ident, Span, kw}; use rustc_span::{Ident, Span, kw, sym};
use thin_vec::ThinVec; use thin_vec::ThinVec;
use super::{ForceCollect, Parser, Trailing, UsePreAttrPos}; 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. /// Parses an optional where-clause.
/// ///
/// ```ignore (only-for-syntax-highlight) /// ```ignore (only-for-syntax-highlight)

View file

@ -213,9 +213,12 @@ impl<'a> Parser<'a> {
self.parse_use_item()? self.parse_use_item()?
} else if self.check_fn_front_matter(check_pub, case) { } else if self.check_fn_front_matter(check_pub, case) {
// FUNCTION ITEM // FUNCTION ITEM
let (ident, sig, generics, body) = let (ident, sig, generics, contract, body) =
self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?; 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)) { } else if self.eat_keyword(exp!(Extern)) {
if self.eat_keyword(exp!(Crate)) { if self.eat_keyword(exp!(Crate)) {
// EXTERN CRATE // EXTERN CRATE
@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> {
sig_lo: Span, sig_lo: Span,
vis: &Visibility, vis: &Visibility,
case: Case, 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 fn_span = self.token.span;
let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn` let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn`
let ident = self.parse_ident()?; // `foo` let ident = self.parse_ident()?; // `foo`
@ -2398,6 +2401,8 @@ impl<'a> Parser<'a> {
// inside `parse_fn_body()`. // inside `parse_fn_body()`.
let fn_params_end = self.prev_token.span.shrink_to_hi(); 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` generics.where_clause = self.parse_where_clause()?; // `where T: Ord`
// `fn_params_end` is needed only when it's followed by a where clause. // `fn_params_end` is needed only when it's followed by a where clause.
@ -2409,7 +2414,7 @@ impl<'a> Parser<'a> {
let body = let body =
self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?; 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); 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 /// Provide diagnostics when function body is not found

View file

@ -83,6 +83,8 @@ pub enum TokenType {
KwCatch, KwCatch,
KwConst, KwConst,
KwContinue, KwContinue,
KwContractEnsures,
KwContractRequires,
KwCrate, KwCrate,
KwDefault, KwDefault,
KwDyn, KwDyn,
@ -217,6 +219,8 @@ impl TokenType {
KwCatch, KwCatch,
KwConst, KwConst,
KwContinue, KwContinue,
KwContractEnsures,
KwContractRequires,
KwCrate, KwCrate,
KwDefault, KwDefault,
KwDyn, KwDyn,
@ -289,6 +293,8 @@ impl TokenType {
TokenType::KwCatch => Some(kw::Catch), TokenType::KwCatch => Some(kw::Catch),
TokenType::KwConst => Some(kw::Const), TokenType::KwConst => Some(kw::Const),
TokenType::KwContinue => Some(kw::Continue), TokenType::KwContinue => Some(kw::Continue),
TokenType::KwContractEnsures => Some(kw::ContractEnsures),
TokenType::KwContractRequires => Some(kw::ContractRequires),
TokenType::KwCrate => Some(kw::Crate), TokenType::KwCrate => Some(kw::Crate),
TokenType::KwDefault => Some(kw::Default), TokenType::KwDefault => Some(kw::Default),
TokenType::KwDyn => Some(kw::Dyn), TokenType::KwDyn => Some(kw::Dyn),
@ -519,6 +525,8 @@ macro_rules! exp {
(Catch) => { exp!(@kw, Catch, KwCatch) }; (Catch) => { exp!(@kw, Catch, KwCatch) };
(Const) => { exp!(@kw, Const, KwConst) }; (Const) => { exp!(@kw, Const, KwConst) };
(Continue) => { exp!(@kw, Continue, KwContinue) }; (Continue) => { exp!(@kw, Continue, KwContinue) };
(ContractEnsures) => { exp!(@kw, ContractEnsures, KwContractEnsures) };
(ContractRequires) => { exp!(@kw, ContractRequires, KwContractRequires) };
(Crate) => { exp!(@kw, Crate, KwCrate) }; (Crate) => { exp!(@kw, Crate, KwCrate) };
(Default) => { exp!(@kw, Default, KwDefault) }; (Default) => { exp!(@kw, Default, KwDefault) };
(Dyn) => { exp!(@kw, Dyn, KwDyn) }; (Dyn) => { exp!(@kw, Dyn, KwDyn) };

View file

@ -174,10 +174,13 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
_ctxt, _ctxt,
_ident, _ident,
_vis, _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 => { ) if let Some(coroutine_kind) = header.coroutine_kind => {
self.visit_fn_header(header); self.visit_fn_header(header);
self.visit_generics(generics); 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 // For async functions, we need to create their inner defs inside of a
// closure to match their desugared representation. Besides that, // closure to match their desugared representation. Besides that,

View file

@ -1019,7 +1019,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
// Create a label rib for the function. // Create a label rib for the function.
this.with_label_rib(RibKind::FnOrCoroutine, |this| { this.with_label_rib(RibKind::FnOrCoroutine, |this| {
match fn_kind { match fn_kind {
FnKind::Fn(_, _, _, Fn { sig, generics, body, .. }) => { FnKind::Fn(_, _, _, Fn { sig, generics, contract, body, .. }) => {
this.visit_generics(generics); this.visit_generics(generics);
let declaration = &sig.decl; 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 { if let Some(body) = body {
// Ignore errors in function bodies if this is rustdoc // Ignore errors in function bodies if this is rustdoc
// Be sure not to set this until the function signature has been resolved. // Be sure not to set this until the function signature has been resolved.

View file

@ -119,6 +119,7 @@ pub(crate) fn disallow_cfgs(sess: &Session, user_cfgs: &Cfg) {
(sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"), (sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"),
(sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"), (sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"),
(sym::ub_checks, None) => disallow(cfg, "-Z ub-checks"), (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::sanitize, None | Some(_)) => disallow(cfg, "-Z sanitizer"),
( (
sym::sanitizer_cfi_generalize_pointers | sym::sanitizer_cfi_normalize_integers, 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 { if sess.is_nightly_build() && sess.opts.unstable_opts.emscripten_wasm_eh {
ins_none!(sym::emscripten_wasm_eh); ins_none!(sym::emscripten_wasm_eh);
} }
if sess.contract_checks() {
ins_none!(sym::contract_checks);
}
ret ret
} }
@ -464,6 +470,7 @@ impl CheckCfg {
ins!(sym::target_thread_local, no_values); ins!(sym::target_thread_local, no_values);
ins!(sym::ub_checks, no_values); ins!(sym::ub_checks, no_values);
ins!(sym::contract_checks, no_values);
ins!(sym::unix, no_values); ins!(sym::unix, no_values);
ins!(sym::windows, no_values); ins!(sym::windows, no_values);

View file

@ -2114,6 +2114,8 @@ options! {
"the backend to use"), "the backend to use"),
combine_cgu: bool = (false, parse_bool, [TRACKED], combine_cgu: bool = (false, parse_bool, [TRACKED],
"combine CGUs into a single one"), "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], coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED],
"control details of coverage instrumentation"), "control details of coverage instrumentation"),
crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED], crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],

View file

@ -207,6 +207,10 @@ pub struct ParseSess {
pub config: Cfg, pub config: Cfg,
pub check_config: CheckCfg, pub check_config: CheckCfg,
pub edition: Edition, 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 /// Places where raw identifiers were used. This is used to avoid complaining about idents
/// clashing with keywords in new editions. /// clashing with keywords in new editions.
pub raw_identifier_spans: AppendOnlyVec<Span>, pub raw_identifier_spans: AppendOnlyVec<Span>,
@ -255,6 +259,7 @@ impl ParseSess {
config: Cfg::default(), config: Cfg::default(),
check_config: CheckCfg::default(), check_config: CheckCfg::default(),
edition: ExpnId::root().expn_data().edition, edition: ExpnId::root().expn_data().edition,
contract_attribute_spans: Default::default(),
raw_identifier_spans: Default::default(), raw_identifier_spans: Default::default(),
bad_unicode_identifiers: Lock::new(Default::default()), bad_unicode_identifiers: Lock::new(Default::default()),
source_map, source_map,

View file

@ -709,6 +709,10 @@ impl Session {
self.opts.unstable_opts.ub_checks.unwrap_or(self.opts.debug_assertions) 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 { pub fn relocation_model(&self) -> RelocModel {
self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model) self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model)
} }

View file

@ -291,6 +291,7 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> {
indices.iter().map(|idx| idx.stable(tables)).collect(), indices.iter().map(|idx| idx.stable(tables)).collect(),
), ),
UbChecks => stable_mir::mir::NullOp::UbChecks, UbChecks => stable_mir::mir::NullOp::UbChecks,
ContractChecks => stable_mir::mir::NullOp::ContractChecks,
} }
} }
} }

View file

@ -1163,6 +1163,8 @@ pub enum DesugaringKind {
WhileLoop, WhileLoop,
/// `async Fn()` bound modifier /// `async Fn()` bound modifier
BoundModifier, BoundModifier,
/// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond)
Contract,
} }
impl DesugaringKind { impl DesugaringKind {
@ -1179,6 +1181,7 @@ impl DesugaringKind {
DesugaringKind::ForLoop => "`for` loop", DesugaringKind::ForLoop => "`for` loop",
DesugaringKind::WhileLoop => "`while` loop", DesugaringKind::WhileLoop => "`while` loop",
DesugaringKind::BoundModifier => "trait bound modifier", DesugaringKind::BoundModifier => "trait bound modifier",
DesugaringKind::Contract => "contract check",
} }
} }
} }

View file

@ -118,6 +118,8 @@ symbols! {
MacroRules: "macro_rules", MacroRules: "macro_rules",
Raw: "raw", Raw: "raw",
Reuse: "reuse", Reuse: "reuse",
ContractEnsures: "contract_ensures",
ContractRequires: "contract_requires",
Safe: "safe", Safe: "safe",
Union: "union", Union: "union",
Yeet: "yeet", Yeet: "yeet",
@ -569,6 +571,7 @@ symbols! {
cfg_attr, cfg_attr,
cfg_attr_multi, cfg_attr_multi,
cfg_boolean_literals, cfg_boolean_literals,
cfg_contract_checks,
cfg_doctest, cfg_doctest,
cfg_emscripten_wasm_eh, cfg_emscripten_wasm_eh,
cfg_eval, cfg_eval,
@ -678,6 +681,14 @@ symbols! {
const_ty_placeholder: "<const_ty>", const_ty_placeholder: "<const_ty>",
constant, constant,
constructor, constructor,
contract_build_check_ensures,
contract_check_ensures,
contract_check_requires,
contract_checks,
contracts,
contracts_ensures,
contracts_internals,
contracts_requires,
convert_identity, convert_identity,
copy, copy,
copy_closures, copy_closures,

View file

@ -608,7 +608,8 @@ impl Rvalue {
Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
Ok(Ty::usize_ty()) 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 { Rvalue::Aggregate(ak, ops) => match *ak {
AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64), AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64),
AggregateKind::Tuple => Ok(Ty::new_tuple( AggregateKind::Tuple => Ok(Ty::new_tuple(
@ -1007,6 +1008,8 @@ pub enum NullOp {
OffsetOf(Vec<(VariantIdx, FieldIdx)>), OffsetOf(Vec<(VariantIdx, FieldIdx)>),
/// cfg!(ub_checks), but at codegen time /// cfg!(ub_checks), but at codegen time
UbChecks, UbChecks,
/// cfg!(contract_checks), but at codegen time
ContractChecks,
} }
impl Operand { impl Operand {

View 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
}
}

View file

@ -4064,6 +4064,52 @@ pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize)
// Runtime NOP // 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. /// The intrinsic will return the size stored in that vtable.
/// ///
/// # Safety /// # Safety

View file

@ -113,6 +113,7 @@
#![feature(bigint_helper_methods)] #![feature(bigint_helper_methods)]
#![feature(bstr)] #![feature(bstr)]
#![feature(bstr_internals)] #![feature(bstr_internals)]
#![feature(closure_track_caller)]
#![feature(const_carrying_mul_add)] #![feature(const_carrying_mul_add)]
#![feature(const_eval_select)] #![feature(const_eval_select)]
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
@ -247,6 +248,10 @@ pub mod autodiff {
pub use crate::macros::builtin::autodiff; pub use crate::macros::builtin::autodiff;
} }
#[cfg(not(bootstrap))]
#[unstable(feature = "contracts", issue = "128044")]
pub mod contracts;
#[unstable(feature = "cfg_match", issue = "115585")] #[unstable(feature = "cfg_match", issue = "115585")]
pub use crate::macros::cfg_match; pub use crate::macros::cfg_match;

View file

@ -1777,6 +1777,32 @@ pub(crate) mod builtin {
/* compiler built-in */ /* 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. /// 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). /// See also [`std::alloc::handle_alloc_error`](../../../std/alloc/fn.handle_alloc_error.html).

View file

@ -362,18 +362,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
defaultness: ld, defaultness: ld,
sig: lf, sig: lf,
generics: lg, generics: lg,
contract: lc,
body: lb, body: lb,
}), }),
Fn(box ast::Fn { Fn(box ast::Fn {
defaultness: rd, defaultness: rd,
sig: rf, sig: rf,
generics: rg, generics: rg,
contract: rc,
body: rb, body: rb,
}), }),
) => { ) => {
eq_defaultness(*ld, *rd) eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf) && eq_fn_sig(lf, rf)
&& eq_generics(lg, rg) && eq_generics(lg, rg)
&& eq_opt_fn_contract(lc, rc)
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) && both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
}, },
(Mod(lu, lmk), Mod(ru, rmk)) => { (Mod(lu, lmk), Mod(ru, rmk)) => {
@ -497,18 +500,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
defaultness: ld, defaultness: ld,
sig: lf, sig: lf,
generics: lg, generics: lg,
contract: lc,
body: lb, body: lb,
}), }),
Fn(box ast::Fn { Fn(box ast::Fn {
defaultness: rd, defaultness: rd,
sig: rf, sig: rf,
generics: rg, generics: rg,
contract: rc,
body: rb, body: rb,
}), }),
) => { ) => {
eq_defaultness(*ld, *rd) eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf) && eq_fn_sig(lf, rf)
&& eq_generics(lg, rg) && eq_generics(lg, rg)
&& eq_opt_fn_contract(lc, rc)
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) && 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, defaultness: ld,
sig: lf, sig: lf,
generics: lg, generics: lg,
contract: lc,
body: lb, body: lb,
}), }),
Fn(box ast::Fn { Fn(box ast::Fn {
defaultness: rd, defaultness: rd,
sig: rf, sig: rf,
generics: rg, generics: rg,
contract: rc,
body: rb, body: rb,
}), }),
) => { ) => {
eq_defaultness(*ld, *rd) eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf) && eq_fn_sig(lf, rf)
&& eq_generics(lg, rg) && eq_generics(lg, rg)
&& eq_opt_fn_contract(lc, rc)
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r)) && 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) && 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 { pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
over(&l.params, &r.params, eq_generic_param) over(&l.params, &r.params, eq_generic_param)
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| { && over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {

View file

@ -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::ShallowInitBox(_, _) => Ok(()),
Rvalue::UnaryOp(_, operand) => { Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx); let ty = operand.ty(body, tcx);

View file

@ -1150,6 +1150,11 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
interp_ok(ecx.tcx.sess.ub_checks()) 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)] #[inline(always)]
fn thread_local_static_pointer( fn thread_local_static_pointer(
ecx: &mut MiriInterpCx<'tcx>, ecx: &mut MiriInterpCx<'tcx>,

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `has_foo`
LL | #[cfg(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: consider using a Cargo feature instead
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
[lints.rust] [lints.rust]

View file

@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
LL | #[cfg(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: consider using a Cargo feature instead
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
[lints.rust] [lints.rust]

View file

@ -25,7 +25,7 @@ warning: unexpected `cfg` condition name: `tokio_unstable`
LL | #[cfg(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: consider using a Cargo feature instead
= help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint: = help: or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:
[lints.rust] [lints.rust]

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `value`
LL | #[cfg(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)` = 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: 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 = note: `#[warn(unexpected_cfgs)]` on by default

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_value`
LL | #[cfg(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)` = 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: 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 = note: `#[warn(unexpected_cfgs)]` on by default

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
LL | #[cfg(unknown_key = "value")] 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"))` = 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: 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 = note: `#[warn(unexpected_cfgs)]` on by default

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `unknown_key`
LL | #[cfg(unknown_key = "value")] 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"))` = 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: 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 = note: `#[warn(unexpected_cfgs)]` on by default

View file

@ -44,7 +44,7 @@ warning: unexpected `cfg` condition name: `uu`
LL | #[cfg_attr(uu, unix)] 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)` = 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 = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration

View file

@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false`
LL | #[cfg(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)` = 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 = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration

View file

@ -14,7 +14,7 @@ warning: unexpected `cfg` condition name: `r#false`
LL | #[cfg(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)` = 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 = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg`
LL | cfg_macro::my_lib_macro!(); 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 = 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: 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` = 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`

View file

@ -4,7 +4,7 @@ warning: unexpected `cfg` condition name: `my_lib_cfg`
LL | cfg_macro::my_lib_macro!(); 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 = 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: 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)` = help: to expect this configuration use `--check-cfg=cfg(my_lib_cfg)`

View file

@ -5,6 +5,7 @@ LL | #[cfg(list_all_well_known_cfgs)]
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^
| |
= help: expected names are: `clippy` = help: expected names are: `clippy`
`contract_checks`
`debug_assertions` `debug_assertions`
`doc` `doc`
`doctest` `doctest`

View 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() {
}

View 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

View 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-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

View 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-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

View 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-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

View 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-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

View 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);
}

View 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-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

View 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

View 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

View 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

View 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);
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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);
}

View 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

View 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

View 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

View 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.
}

View 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

View 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);
}

View file

@ -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`.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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