Guard HIR lowered contracts with contract_checks
Refactor contract HIR lowering to ensure no contract code is executed when contract-checks are disabled. The call to contract_checks is moved to inside the lowered fn body, and contract closures are built conditionally, ensuring no side-effects present in contracts occur when those are disabled.
This commit is contained in:
parent
5b9007bfc3
commit
e4ead0ec70
18 changed files with 461 additions and 120 deletions
260
compiler/rustc_ast_lowering/src/contract.rs
Normal file
260
compiler/rustc_ast_lowering/src/contract.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
use crate::LoweringContext;
|
||||
|
||||
impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||
pub(super) fn lower_contract(
|
||||
&mut self,
|
||||
body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
|
||||
contract: &rustc_ast::FnContract,
|
||||
) -> rustc_hir::Expr<'hir> {
|
||||
match (&contract.requires, &contract.ensures) {
|
||||
(Some(req), Some(ens)) => {
|
||||
// Lower the fn contract, which turns:
|
||||
//
|
||||
// { body }
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// {
|
||||
// let __postcond = if contracts_checks() {
|
||||
// contract_check_requires(PRECOND);
|
||||
// Some(|ret_val| POSTCOND)
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
// contract_check_ensures(__postcond, { body })
|
||||
// }
|
||||
|
||||
let precond = self.lower_precond(req);
|
||||
let postcond_checker = self.lower_postcond_checker(ens);
|
||||
|
||||
let contract_check =
|
||||
self.lower_contract_check_with_postcond(Some(precond), postcond_checker);
|
||||
|
||||
let wrapped_body =
|
||||
self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
|
||||
self.expr_block(wrapped_body)
|
||||
}
|
||||
(None, Some(ens)) => {
|
||||
// Lower the fn contract, which turns:
|
||||
//
|
||||
// { body }
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// {
|
||||
// let __postcond = if contracts_check() {
|
||||
// Some(|ret_val| POSTCOND)
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
// __postcond({ body })
|
||||
// }
|
||||
|
||||
let postcond_checker = self.lower_postcond_checker(ens);
|
||||
let contract_check =
|
||||
self.lower_contract_check_with_postcond(None, postcond_checker);
|
||||
|
||||
let wrapped_body =
|
||||
self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
|
||||
self.expr_block(wrapped_body)
|
||||
}
|
||||
(Some(req), None) => {
|
||||
// Lower the fn contract, which turns:
|
||||
//
|
||||
// { body }
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// {
|
||||
// if contracts_check() {
|
||||
// contract_requires(PRECOND);
|
||||
// }
|
||||
// body
|
||||
// }
|
||||
let precond = self.lower_precond(req);
|
||||
let precond_check = self.lower_contract_check_just_precond(precond);
|
||||
|
||||
let body = self.arena.alloc(body(self));
|
||||
|
||||
// Flatten the body into precond check, then body.
|
||||
let wrapped_body = self.block_all(
|
||||
body.span,
|
||||
self.arena.alloc_from_iter([precond_check].into_iter()),
|
||||
Some(body),
|
||||
);
|
||||
self.expr_block(wrapped_body)
|
||||
}
|
||||
(None, None) => body(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower the precondition check intrinsic.
|
||||
fn lower_precond(&mut self, req: &Box<rustc_ast::Expr>) -> rustc_hir::Stmt<'hir> {
|
||||
let lowered_req = self.lower_expr_mut(&req);
|
||||
let req_span = self.mark_span_with_reason(
|
||||
rustc_span::DesugaringKind::Contract,
|
||||
lowered_req.span,
|
||||
None,
|
||||
);
|
||||
let precond = self.expr_call_lang_item_fn_mut(
|
||||
req_span,
|
||||
rustc_hir::LangItem::ContractCheckRequires,
|
||||
&*arena_vec![self; lowered_req],
|
||||
);
|
||||
self.stmt_expr(req.span, precond)
|
||||
}
|
||||
|
||||
fn lower_postcond_checker(
|
||||
&mut self,
|
||||
ens: &Box<rustc_ast::Expr>,
|
||||
) -> &'hir rustc_hir::Expr<'hir> {
|
||||
let ens_span = self.lower_span(ens.span);
|
||||
let ens_span =
|
||||
self.mark_span_with_reason(rustc_span::DesugaringKind::Contract, ens_span, None);
|
||||
let lowered_ens = self.lower_expr_mut(&ens);
|
||||
self.expr_call_lang_item_fn(
|
||||
ens_span,
|
||||
rustc_hir::LangItem::ContractBuildCheckEnsures,
|
||||
&*arena_vec![self; lowered_ens],
|
||||
)
|
||||
}
|
||||
|
||||
fn lower_contract_check_just_precond(
|
||||
&mut self,
|
||||
precond: rustc_hir::Stmt<'hir>,
|
||||
) -> rustc_hir::Stmt<'hir> {
|
||||
let stmts = self.arena.alloc_from_iter([precond].into_iter());
|
||||
|
||||
let then_block_stmts = self.block_all(precond.span, stmts, None);
|
||||
let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));
|
||||
|
||||
let precond_check = rustc_hir::ExprKind::If(
|
||||
self.expr_call_lang_item_fn(
|
||||
precond.span,
|
||||
rustc_hir::LangItem::ContractChecks,
|
||||
Default::default(),
|
||||
),
|
||||
then_block,
|
||||
None,
|
||||
);
|
||||
|
||||
let precond_check = self.expr(precond.span, precond_check);
|
||||
self.stmt_expr(precond.span, precond_check)
|
||||
}
|
||||
|
||||
fn lower_contract_check_with_postcond(
|
||||
&mut self,
|
||||
precond: Option<rustc_hir::Stmt<'hir>>,
|
||||
postcond_checker: &'hir rustc_hir::Expr<'hir>,
|
||||
) -> &'hir rustc_hir::Expr<'hir> {
|
||||
let stmts = self.arena.alloc_from_iter(precond.into_iter());
|
||||
let span = match precond {
|
||||
Some(precond) => precond.span,
|
||||
None => postcond_checker.span,
|
||||
};
|
||||
|
||||
let postcond_checker = self.arena.alloc(self.expr_enum_variant_lang_item(
|
||||
postcond_checker.span,
|
||||
rustc_hir::lang_items::LangItem::OptionSome,
|
||||
&*arena_vec![self; *postcond_checker],
|
||||
));
|
||||
let then_block_stmts = self.block_all(span, stmts, Some(postcond_checker));
|
||||
let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));
|
||||
|
||||
let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item(
|
||||
postcond_checker.span,
|
||||
rustc_hir::lang_items::LangItem::OptionNone,
|
||||
Default::default(),
|
||||
));
|
||||
let else_block = self.block_expr(none_expr);
|
||||
let else_block = self.arena.alloc(self.expr_block(else_block));
|
||||
|
||||
let contract_check = rustc_hir::ExprKind::If(
|
||||
self.expr_call_lang_item_fn(
|
||||
span,
|
||||
rustc_hir::LangItem::ContractChecks,
|
||||
Default::default(),
|
||||
),
|
||||
then_block,
|
||||
Some(else_block),
|
||||
);
|
||||
self.arena.alloc(self.expr(span, contract_check))
|
||||
}
|
||||
|
||||
fn wrap_body_with_contract_check(
|
||||
&mut self,
|
||||
body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
|
||||
contract_check: &'hir rustc_hir::Expr<'hir>,
|
||||
postcond_span: rustc_span::Span,
|
||||
) -> &'hir rustc_hir::Block<'hir> {
|
||||
let check_ident: rustc_span::Ident =
|
||||
rustc_span::Ident::from_str_and_span("__ensures_checker", postcond_span);
|
||||
let (check_hir_id, postcond_decl) = {
|
||||
// Set up the postcondition `let` statement.
|
||||
let (checker_pat, check_hir_id) = self.pat_ident_binding_mode_mut(
|
||||
postcond_span,
|
||||
check_ident,
|
||||
rustc_hir::BindingMode::NONE,
|
||||
);
|
||||
(
|
||||
check_hir_id,
|
||||
self.stmt_let_pat(
|
||||
None,
|
||||
postcond_span,
|
||||
Some(contract_check),
|
||||
self.arena.alloc(checker_pat),
|
||||
rustc_hir::LocalSource::Contract,
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
// Install contract_ensures so we will intercept `return` statements,
|
||||
// then lower the body.
|
||||
self.contract_ensures = Some((postcond_span, check_ident, check_hir_id));
|
||||
let body = self.arena.alloc(body(self));
|
||||
|
||||
// Finally, inject an ensures check on the implicit return of the body.
|
||||
let body = self.inject_ensures_check(body, postcond_span, check_ident, check_hir_id);
|
||||
|
||||
// Flatten the body into precond, then postcond, then wrapped body.
|
||||
let wrapped_body = self.block_all(
|
||||
body.span,
|
||||
self.arena.alloc_from_iter([postcond_decl].into_iter()),
|
||||
Some(body),
|
||||
);
|
||||
wrapped_body
|
||||
}
|
||||
|
||||
/// Create an `ExprKind::Ret` that is optionally wrapped by a call to check
|
||||
/// a contract ensures clause, if it exists.
|
||||
pub(super) fn checked_return(
|
||||
&mut self,
|
||||
opt_expr: Option<&'hir rustc_hir::Expr<'hir>>,
|
||||
) -> rustc_hir::ExprKind<'hir> {
|
||||
let checked_ret =
|
||||
if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures {
|
||||
let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span));
|
||||
Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id))
|
||||
} else {
|
||||
opt_expr
|
||||
};
|
||||
rustc_hir::ExprKind::Ret(checked_ret)
|
||||
}
|
||||
|
||||
/// Wraps an expression with a call to the ensures check before it gets returned.
|
||||
pub(super) fn inject_ensures_check(
|
||||
&mut self,
|
||||
expr: &'hir rustc_hir::Expr<'hir>,
|
||||
span: rustc_span::Span,
|
||||
cond_ident: rustc_span::Ident,
|
||||
cond_hir_id: rustc_hir::HirId,
|
||||
) -> &'hir rustc_hir::Expr<'hir> {
|
||||
let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
|
||||
let call_expr = self.expr_call_lang_item_fn_mut(
|
||||
span,
|
||||
rustc_hir::LangItem::ContractCheckEnsures,
|
||||
arena_vec![self; *cond_fn, *expr],
|
||||
);
|
||||
self.arena.alloc(call_expr)
|
||||
}
|
||||
}
|
||||
|
|
@ -383,36 +383,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Create an `ExprKind::Ret` that is optionally wrapped by a call to check
|
||||
/// a contract ensures clause, if it exists.
|
||||
fn checked_return(&mut self, opt_expr: Option<&'hir hir::Expr<'hir>>) -> hir::ExprKind<'hir> {
|
||||
let checked_ret =
|
||||
if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures {
|
||||
let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span));
|
||||
Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id))
|
||||
} 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,
|
||||
cond_ident: Ident,
|
||||
cond_hir_id: HirId,
|
||||
) -> &'hir hir::Expr<'hir> {
|
||||
let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
|
||||
let call_expr = self.expr_call_lang_item_fn_mut(
|
||||
span,
|
||||
hir::LangItem::ContractCheckEnsures,
|
||||
arena_vec![self; *cond_fn, *expr],
|
||||
);
|
||||
self.arena.alloc(call_expr)
|
||||
}
|
||||
|
||||
pub(crate) fn lower_const_block(&mut self, c: &AnonConst) -> hir::ConstBlock {
|
||||
self.with_new_scopes(c.value.span, |this| {
|
||||
let def_id = this.local_def_id(c.id);
|
||||
|
|
@ -2120,7 +2090,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
self.expr(span, hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e))
|
||||
}
|
||||
|
||||
fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> {
|
||||
pub(super) fn expr_unit(&mut self, sp: Span) -> &'hir hir::Expr<'hir> {
|
||||
self.arena.alloc(self.expr(sp, hir::ExprKind::Tup(&[])))
|
||||
}
|
||||
|
||||
|
|
@ -2161,6 +2131,43 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
self.expr(span, hir::ExprKind::Call(e, args))
|
||||
}
|
||||
|
||||
pub(super) fn expr_struct(
|
||||
&mut self,
|
||||
span: Span,
|
||||
path: &'hir hir::QPath<'hir>,
|
||||
fields: &'hir [hir::ExprField<'hir>],
|
||||
) -> hir::Expr<'hir> {
|
||||
self.expr(span, hir::ExprKind::Struct(path, fields, rustc_hir::StructTailExpr::None))
|
||||
}
|
||||
|
||||
pub(super) fn expr_enum_variant(
|
||||
&mut self,
|
||||
span: Span,
|
||||
path: &'hir hir::QPath<'hir>,
|
||||
fields: &'hir [hir::Expr<'hir>],
|
||||
) -> hir::Expr<'hir> {
|
||||
let fields = self.arena.alloc_from_iter(fields.into_iter().enumerate().map(|(i, f)| {
|
||||
hir::ExprField {
|
||||
hir_id: self.next_id(),
|
||||
ident: Ident::from_str(&i.to_string()),
|
||||
expr: f,
|
||||
span: f.span,
|
||||
is_shorthand: false,
|
||||
}
|
||||
}));
|
||||
self.expr_struct(span, path, fields)
|
||||
}
|
||||
|
||||
pub(super) fn expr_enum_variant_lang_item(
|
||||
&mut self,
|
||||
span: Span,
|
||||
lang_item: hir::LangItem,
|
||||
fields: &'hir [hir::Expr<'hir>],
|
||||
) -> hir::Expr<'hir> {
|
||||
let path = self.arena.alloc(self.lang_item_path(span, lang_item));
|
||||
self.expr_enum_variant(span, path, fields)
|
||||
}
|
||||
|
||||
pub(super) fn expr_call(
|
||||
&mut self,
|
||||
span: Span,
|
||||
|
|
@ -2189,8 +2196,21 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
self.arena.alloc(self.expr_call_lang_item_fn_mut(span, lang_item, args))
|
||||
}
|
||||
|
||||
fn expr_lang_item_path(&mut self, span: Span, lang_item: hir::LangItem) -> hir::Expr<'hir> {
|
||||
self.expr(span, hir::ExprKind::Path(hir::QPath::LangItem(lang_item, self.lower_span(span))))
|
||||
pub(super) fn expr_lang_item_path(
|
||||
&mut self,
|
||||
span: Span,
|
||||
lang_item: hir::LangItem,
|
||||
) -> hir::Expr<'hir> {
|
||||
let path = self.lang_item_path(span, lang_item);
|
||||
self.expr(span, hir::ExprKind::Path(path))
|
||||
}
|
||||
|
||||
pub(super) fn lang_item_path(
|
||||
&mut self,
|
||||
span: Span,
|
||||
lang_item: hir::LangItem,
|
||||
) -> hir::QPath<'hir> {
|
||||
hir::QPath::LangItem(lang_item, self.lower_span(span))
|
||||
}
|
||||
|
||||
/// `<LangItem>::name`
|
||||
|
|
|
|||
|
|
@ -1214,76 +1214,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
let params =
|
||||
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
|
||||
|
||||
// Optionally lower the fn contract, which turns:
|
||||
//
|
||||
// { body }
|
||||
//
|
||||
// into:
|
||||
//
|
||||
// { contract_requires(PRECOND); let __postcond = |ret_val| POSTCOND; postcond({ body }) }
|
||||
// Optionally lower the fn contract
|
||||
if let Some(contract) = contract {
|
||||
let precond = if let Some(req) = &contract.requires {
|
||||
// Lower the precondition check intrinsic.
|
||||
let lowered_req = this.lower_expr_mut(&req);
|
||||
let req_span = this.mark_span_with_reason(
|
||||
DesugaringKind::Contract,
|
||||
lowered_req.span,
|
||||
None,
|
||||
);
|
||||
let precond = this.expr_call_lang_item_fn_mut(
|
||||
req_span,
|
||||
hir::LangItem::ContractCheckRequires,
|
||||
&*arena_vec![this; lowered_req],
|
||||
);
|
||||
Some(this.stmt_expr(req.span, precond))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (postcond, body) = if let Some(ens) = &contract.ensures {
|
||||
let ens_span = this.lower_span(ens.span);
|
||||
let ens_span =
|
||||
this.mark_span_with_reason(DesugaringKind::Contract, ens_span, None);
|
||||
// Set up the postcondition `let` statement.
|
||||
let check_ident: Ident =
|
||||
Ident::from_str_and_span("__ensures_checker", ens_span);
|
||||
let (checker_pat, check_hir_id) = this.pat_ident_binding_mode_mut(
|
||||
ens_span,
|
||||
check_ident,
|
||||
hir::BindingMode::NONE,
|
||||
);
|
||||
let lowered_ens = 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 postcond = this.stmt_let_pat(
|
||||
None,
|
||||
ens_span,
|
||||
Some(postcond_checker),
|
||||
this.arena.alloc(checker_pat),
|
||||
hir::LocalSource::Contract,
|
||||
);
|
||||
|
||||
// Install contract_ensures so we will intercept `return` statements,
|
||||
// then lower the body.
|
||||
this.contract_ensures = Some((ens_span, check_ident, check_hir_id));
|
||||
let body = this.arena.alloc(body(this));
|
||||
|
||||
// Finally, inject an ensures check on the implicit return of the body.
|
||||
let body = this.inject_ensures_check(body, ens_span, check_ident, check_hir_id);
|
||||
(Some(postcond), body)
|
||||
} else {
|
||||
let body = &*this.arena.alloc(body(this));
|
||||
(None, body)
|
||||
};
|
||||
// Flatten the body into precond, then postcond, then wrapped body.
|
||||
let wrapped_body = this.block_all(
|
||||
body.span,
|
||||
this.arena.alloc_from_iter([precond, postcond].into_iter().flatten()),
|
||||
Some(body),
|
||||
);
|
||||
(params, this.expr_block(wrapped_body))
|
||||
(params, this.lower_contract(body, contract))
|
||||
} else {
|
||||
(params, body(this))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ macro_rules! arena_vec {
|
|||
|
||||
mod asm;
|
||||
mod block;
|
||||
mod contract;
|
||||
mod delegation;
|
||||
mod errors;
|
||||
mod expr;
|
||||
|
|
|
|||
|
|
@ -429,6 +429,7 @@ language_item_table! {
|
|||
// 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;
|
||||
ContractChecks, sym::contract_checks, contract_checks_fn, Target::Fn, GenericRequirement::None;
|
||||
|
||||
// Experimental lang items for `MCP: Low level components for async drop`(https://github.com/rust-lang/compiler-team/issues/727)
|
||||
DefaultTrait4, sym::default_trait4, default_trait4_trait, Target::Trait, GenericRequirement::None;
|
||||
|
|
|
|||
|
|
@ -651,7 +651,9 @@ pub(crate) fn check_intrinsic_type(
|
|||
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::contract_check_ensures => (2, 0, vec![param(0), param(1)], param(1)),
|
||||
sym::contract_check_ensures => {
|
||||
(2, 0, vec![Ty::new_option(tcx, param(0)), param(1)], param(1))
|
||||
}
|
||||
|
||||
sym::simd_eq | sym::simd_ne | sym::simd_lt | sym::simd_le | sym::simd_gt | sym::simd_ge => {
|
||||
(2, 0, vec![param(0), param(0)], param(1))
|
||||
|
|
|
|||
|
|
@ -903,6 +903,12 @@ impl<'tcx> Ty<'tcx> {
|
|||
Ty::new_generic_adt(tcx, def_id, ty)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_option(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
let def_id = tcx.require_lang_item(LangItem::Option, DUMMY_SP);
|
||||
Ty::new_generic_adt(tcx, def_id, ty)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_maybe_uninit(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
let def_id = tcx.require_lang_item(LangItem::MaybeUninit, DUMMY_SP);
|
||||
|
|
|
|||
|
|
@ -2637,9 +2637,10 @@ pub const unsafe fn const_make_global(ptr: *mut u8) -> *const u8 {
|
|||
/// of not prematurely committing 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.
|
||||
#[rustc_const_unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[unstable(feature = "contracts_internals", issue = "128044" /* compiler-team#759 */)]
|
||||
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
|
||||
#[inline(always)]
|
||||
#[lang = "contract_checks"]
|
||||
#[rustc_intrinsic]
|
||||
pub const fn contract_checks() -> bool {
|
||||
// FIXME: should this be `false` or `cfg!(contract_checks)`?
|
||||
|
|
@ -2668,7 +2669,7 @@ pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
|
|||
if const {
|
||||
// Do nothing
|
||||
} else {
|
||||
if contract_checks() && !cond() {
|
||||
if !cond() {
|
||||
// Emit no unwind panic in case this was a safety requirement.
|
||||
crate::panicking::panic_nounwind("failed requires check");
|
||||
}
|
||||
|
|
@ -2681,6 +2682,8 @@ pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
|
|||
/// By default, if `contract_checks` is enabled, this will panic with no unwind if the condition
|
||||
/// returns false.
|
||||
///
|
||||
/// If `cond` is `None`, then no postcondition checking is performed.
|
||||
///
|
||||
/// Note that this function is a no-op during constant evaluation.
|
||||
#[unstable(feature = "contracts_internals", issue = "128044")]
|
||||
// Similar to `contract_check_requires`, we need to use the user-facing
|
||||
|
|
@ -2689,16 +2692,24 @@ pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
|
|||
#[rustc_const_unstable(feature = "contracts", issue = "128044")]
|
||||
#[lang = "contract_check_ensures"]
|
||||
#[rustc_intrinsic]
|
||||
pub const fn contract_check_ensures<C: Fn(&Ret) -> bool + Copy, Ret>(cond: C, ret: Ret) -> Ret {
|
||||
pub const fn contract_check_ensures<C: Fn(&Ret) -> bool + Copy, Ret>(
|
||||
cond: Option<C>,
|
||||
ret: Ret,
|
||||
) -> Ret {
|
||||
const_eval_select!(
|
||||
@capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: C, ret: Ret } -> Ret :
|
||||
@capture[C: Fn(&Ret) -> bool + Copy, Ret] { cond: Option<C>, ret: Ret } -> Ret :
|
||||
if const {
|
||||
// Do nothing
|
||||
ret
|
||||
} else {
|
||||
if contract_checks() && !cond(&ret) {
|
||||
// Emit no unwind panic in case this was a safety requirement.
|
||||
crate::panicking::panic_nounwind("failed ensures check");
|
||||
match cond {
|
||||
crate::option::Option::Some(cond) => {
|
||||
if !cond(&ret) {
|
||||
// Emit no unwind panic in case this was a safety requirement.
|
||||
crate::panicking::panic_nounwind("failed ensures check");
|
||||
}
|
||||
},
|
||||
crate::option::Option::None => {},
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
|
|
|||
17
tests/ui/contracts/contracts-disabled-side-effect-ensures.rs
Normal file
17
tests/ui/contracts/contracts-disabled-side-effect-ensures.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//@ run-pass
|
||||
#![feature(contracts)]
|
||||
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]
|
||||
|
||||
extern crate core;
|
||||
use core::contracts::ensures;
|
||||
|
||||
#[ensures({*x = 0; |_ret| true})]
|
||||
fn buggy_add(x: &mut u32, y: u32) {
|
||||
*x = *x + y;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut x = 10;
|
||||
buggy_add(&mut x, 100);
|
||||
assert_eq!(x, 110);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/contracts-disabled-side-effect-ensures.rs:2: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
|
||||
|
||||
16
tests/ui/contracts/empty-ensures.rs
Normal file
16
tests/ui/contracts/empty-ensures.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//@ 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]
|
||||
|
||||
extern crate core;
|
||||
use core::contracts::ensures;
|
||||
|
||||
#[ensures()]
|
||||
//~^ ERROR expected a `Fn(&_)` closure, found `()` [E0277]
|
||||
fn foo(x: u32) -> u32 {
|
||||
x * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
foo(1);
|
||||
}
|
||||
25
tests/ui/contracts/empty-ensures.stderr
Normal file
25
tests/ui/contracts/empty-ensures.stderr
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/empty-ensures.rs:2: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]: expected a `Fn(&_)` closure, found `()`
|
||||
--> $DIR/empty-ensures.rs:8:1
|
||||
|
|
||||
LL | #[ensures()]
|
||||
| ^^^^^^^^^^^^
|
||||
| |
|
||||
| expected an `Fn(&_)` closure, found `()`
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `for<'a> Fn(&'a _)` is not implemented for `()`
|
||||
note: required by a bound in `build_check_ensures`
|
||||
--> $SRC_DIR/core/src/contracts.rs:LL:COL
|
||||
|
||||
error: aborting due to 1 previous error; 1 warning emitted
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
||||
18
tests/ui/contracts/empty-requires.rs
Normal file
18
tests/ui/contracts/empty-requires.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//@ dont-require-annotations: NOTE
|
||||
//@ 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]
|
||||
|
||||
extern crate core;
|
||||
use core::contracts::requires;
|
||||
|
||||
#[requires()]
|
||||
//~^ ERROR mismatched types [E0308]
|
||||
//~| NOTE expected `bool`, found `()`
|
||||
fn foo(x: u32) -> u32 {
|
||||
x * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
foo(1);
|
||||
}
|
||||
18
tests/ui/contracts/empty-requires.stderr
Normal file
18
tests/ui/contracts/empty-requires.stderr
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
|
||||
--> $DIR/empty-requires.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[E0308]: mismatched types
|
||||
--> $DIR/empty-requires.rs:9:1
|
||||
|
|
||||
LL | #[requires()]
|
||||
| ^^^^^^^^^^^^^ expected `bool`, found `()`
|
||||
|
||||
error: aborting due to 1 previous error; 1 warning emitted
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
|
|
@ -22,15 +22,15 @@ fn main() {
|
|||
// always pass
|
||||
core::intrinsics::contract_check_requires(|| true);
|
||||
|
||||
// fail if enabled
|
||||
#[cfg(any(default, unchk_pass, chk_fail_requires))]
|
||||
// always fail
|
||||
#[cfg(any(chk_fail_requires))]
|
||||
core::intrinsics::contract_check_requires(|| false);
|
||||
|
||||
let doubles_to_two = { let old = 2; move |ret: &u32 | ret + ret == old };
|
||||
// Always pass
|
||||
core::intrinsics::contract_check_ensures(doubles_to_two, 1);
|
||||
core::intrinsics::contract_check_ensures(Some(doubles_to_two), 1);
|
||||
|
||||
// Fail if enabled
|
||||
#[cfg(any(default, unchk_pass, chk_fail_ensures))]
|
||||
core::intrinsics::contract_check_ensures(doubles_to_two, 2);
|
||||
// always fail
|
||||
#[cfg(any(chk_fail_ensures))]
|
||||
core::intrinsics::contract_check_ensures(Some(doubles_to_two), 2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
#![feature(contracts_internals)] // to access check_requires lang item
|
||||
#![feature(core_intrinsics)]
|
||||
fn foo(x: Baz) -> i32 {
|
||||
let injected_checker = {
|
||||
core::contracts::build_check_ensures(|ret| *ret > 100)
|
||||
let injected_checker = if core::intrinsics::contract_checks() {
|
||||
Some(core::contracts::build_check_ensures(|ret| *ret > 100))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ret = x.baz + 50;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ fn main() {
|
|||
//~^ ERROR use of unstable library feature `contracts_internals`
|
||||
core::intrinsics::contract_check_requires(|| true);
|
||||
//~^ ERROR use of unstable library feature `contracts_internals`
|
||||
core::intrinsics::contract_check_ensures( |_|true, &1);
|
||||
core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1);
|
||||
//~^ ERROR use of unstable library feature `contracts_internals`
|
||||
|
||||
core::contracts::build_check_ensures(|_: &()| true);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ LL | core::intrinsics::contract_check_requires(|| true);
|
|||
error[E0658]: use of unstable library feature `contracts_internals`
|
||||
--> $DIR/internal-feature-gating.rs:9:5
|
||||
|
|
||||
LL | core::intrinsics::contract_check_ensures( |_|true, &1);
|
||||
LL | core::intrinsics::contract_check_ensures(Some(|_: &&u32| true), &1);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue