Skip to main content

rustc_ast_lowering/
contract.rs

1use std::sync::Arc;
2
3use rustc_attr_ir::lang_items::LangItem;
4use rustc_attr_ir::target::Target;
5use thin_vec::thin_vec;
6
7use crate::LoweringContext;
8
9impl<'hir> LoweringContext<'_, 'hir> {
10    /// Lowered contracts are guarded with the `contract_checks` compiler flag,
11    /// i.e. the flag turns into a boolean guard in the lowered HIR. The reason
12    /// for not eliminating the contract code entirely when the `contract_checks`
13    /// flag is disabled is so that contracts can be type checked, even when
14    /// they are disabled, which avoids them becoming stale (i.e. out of sync
15    /// with the codebase) over time.
16    ///
17    /// The optimiser should be able to eliminate all contract code guarded
18    /// by `if false`, leaving the original body intact when runtime contract
19    /// checks are disabled.
20    pub(super) fn lower_contract(
21        &mut self,
22        body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
23        contract: &rustc_ast::FnContract,
24    ) -> rustc_hir::Expr<'hir> {
25        // The order in which things are lowered is important! I.e to
26        // refer to variables in contract_decls from postcond/precond,
27        // we must lower it first!
28        let contract_decls = self.lower_decls(contract);
29
30        match (&contract.requires, &contract.ensures) {
31            (Some(req), Some(ens)) => {
32                // Lower the fn contract, which turns:
33                //
34                // { body }
35                //
36                // into:
37                //
38                // let __postcond = if contract_checks {
39                //     CONTRACT_DECLARATIONS;
40                //     contract_check_requires(PRECOND);
41                //     Some(|ret_val| POSTCOND)
42                // } else {
43                //     None
44                // };
45                // {
46                //     let ret = { body };
47                //
48                //     if contract_checks {
49                //         contract_check_ensures(__postcond, ret)
50                //     } else {
51                //         ret
52                //     }
53                // }
54
55                let precond = self.lower_precond(req);
56                let postcond_checker = self.lower_postcond_checker(ens);
57
58                let contract_check = self.lower_contract_check_with_postcond(
59                    contract_decls,
60                    Some(precond),
61                    postcond_checker,
62                );
63
64                let wrapped_body =
65                    self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
66                self.expr_block(wrapped_body)
67            }
68            (None, Some(ens)) => {
69                // Lower the fn contract, which turns:
70                //
71                // { body }
72                //
73                // into:
74                //
75                // let __postcond = if contract_checks {
76                //     Some(|ret_val| POSTCOND)
77                // } else {
78                //     None
79                // };
80                // {
81                //     let ret = { body };
82                //
83                //     if contract_checks {
84                //         CONTRACT_DECLARATIONS;
85                //         contract_check_ensures(__postcond, ret)
86                //     } else {
87                //         ret
88                //     }
89                // }
90                let postcond_checker = self.lower_postcond_checker(ens);
91                let contract_check =
92                    self.lower_contract_check_with_postcond(contract_decls, None, postcond_checker);
93
94                let wrapped_body =
95                    self.wrap_body_with_contract_check(body, contract_check, postcond_checker.span);
96                self.expr_block(wrapped_body)
97            }
98            (Some(req), None) => {
99                // Lower the fn contract, which turns:
100                //
101                // { body }
102                //
103                // into:
104                //
105                // {
106                //      if contracts_checks {
107                //          CONTRACT_DECLARATIONS;
108                //          contract_requires(PRECOND);
109                //      }
110                //      body
111                // }
112                let precond = self.lower_precond(req);
113                let precond_check = self.lower_contract_check_just_precond(contract_decls, precond);
114
115                let body = self.arena.alloc(body(self));
116
117                // Flatten the body into precond check, then body.
118                let wrapped_body = self.block_all(
119                    body.span,
120                    self.arena.alloc_from_iter([precond_check].into_iter()),
121                    Some(body),
122                );
123                self.expr_block(wrapped_body)
124            }
125            (None, None) => body(self),
126        }
127    }
128
129    fn lower_decls(&mut self, contract: &rustc_ast::FnContract) -> &'hir [rustc_hir::Stmt<'hir>] {
130        let (decls, decls_tail) = self.lower_stmts(&contract.declarations);
131
132        if let Some(e) = decls_tail {
133            // include the tail expression in the declaration statements
134            let tail = self.stmt_expr(e.span, *e);
135            self.arena.alloc_from_iter(decls.into_iter().map(|d| *d).chain([tail].into_iter()))
136        } else {
137            decls
138        }
139    }
140
141    /// Lower the precondition check intrinsic.
142    fn lower_precond(&mut self, req: &Box<rustc_ast::Expr>) -> rustc_hir::Stmt<'hir> {
143        let lowered_req = self.lower_expr_mut(&req);
144        let req_span = self.mark_span_with_reason(
145            rustc_span::DesugaringKind::Contract,
146            lowered_req.span,
147            Some(Arc::clone(&crate::ALLOW_CONTRACTS)),
148        );
149        let precond = self.expr_call_lang_item_fn_mut(
150            req_span,
151            LangItem::ContractCheckRequires,
152            &*self.arena.alloc_from_iter([lowered_req])arena_vec![self; lowered_req],
153        );
154        self.stmt_expr(req.span, precond)
155    }
156
157    fn lower_postcond_checker(
158        &mut self,
159        ens: &Box<rustc_ast::Expr>,
160    ) -> &'hir rustc_hir::Expr<'hir> {
161        let ens_span = self.lower_span(ens.span);
162        let ens_span = self.mark_span_with_reason(
163            rustc_span::DesugaringKind::Contract,
164            ens_span,
165            Some(Arc::clone(&crate::ALLOW_CONTRACTS)),
166        );
167        let lowered_ens = self.lower_expr_mut(&ens);
168        self.expr_call_lang_item_fn(
169            ens_span,
170            LangItem::ContractBuildCheckEnsures,
171            &*self.arena.alloc_from_iter([lowered_ens])arena_vec![self; lowered_ens],
172        )
173    }
174
175    fn lower_contract_check_just_precond(
176        &mut self,
177        contract_decls: &'hir [rustc_hir::Stmt<'hir>],
178        precond: rustc_hir::Stmt<'hir>,
179    ) -> rustc_hir::Stmt<'hir> {
180        let stmts = self
181            .arena
182            .alloc_from_iter(contract_decls.into_iter().map(|d| *d).chain([precond].into_iter()));
183
184        let then_block_stmts = self.block_all(precond.span, stmts, None);
185        let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));
186
187        let precond_check = rustc_hir::ExprKind::If(
188            self.arena.alloc(self.expr_bool_literal(precond.span, self.tcx.sess.contract_checks())),
189            then_block,
190            None,
191        );
192
193        let precond_check = self.expr(precond.span, precond_check);
194        self.stmt_expr(precond.span, precond_check)
195    }
196
197    fn lower_contract_check_with_postcond(
198        &mut self,
199        contract_decls: &'hir [rustc_hir::Stmt<'hir>],
200        precond: Option<rustc_hir::Stmt<'hir>>,
201        postcond_checker: &'hir rustc_hir::Expr<'hir>,
202    ) -> &'hir rustc_hir::Expr<'hir> {
203        let stmts = self
204            .arena
205            .alloc_from_iter(contract_decls.into_iter().map(|d| *d).chain(precond.into_iter()));
206        let span = match precond {
207            Some(precond) => precond.span,
208            None => postcond_checker.span,
209        };
210
211        let postcond_checker = self.arena.alloc(self.expr_enum_variant_lang_item(
212            postcond_checker.span,
213            LangItem::OptionSome,
214            &*self.arena.alloc_from_iter([*postcond_checker])arena_vec![self; *postcond_checker],
215        ));
216        let then_block_stmts = self.block_all(span, stmts, Some(postcond_checker));
217        let then_block = self.arena.alloc(self.expr_block(&then_block_stmts));
218
219        let none_expr = self.arena.alloc(self.expr_enum_variant_lang_item(
220            postcond_checker.span,
221            LangItem::OptionNone,
222            Default::default(),
223        ));
224        let else_block = self.block_expr(none_expr);
225        let else_block = self.arena.alloc(self.expr_block(else_block));
226
227        let contract_check = rustc_hir::ExprKind::If(
228            self.arena.alloc(self.expr_bool_literal(span, self.tcx.sess.contract_checks())),
229            then_block,
230            Some(else_block),
231        );
232        self.arena.alloc(self.expr(span, contract_check))
233    }
234
235    fn wrap_body_with_contract_check(
236        &mut self,
237        body: impl FnOnce(&mut Self) -> rustc_hir::Expr<'hir>,
238        contract_check: &'hir rustc_hir::Expr<'hir>,
239        postcond_span: rustc_span::Span,
240    ) -> &'hir rustc_hir::Block<'hir> {
241        let check_ident: rustc_span::Ident =
242            rustc_span::Ident::from_str_and_span("__ensures_checker", postcond_span);
243        let (check_hir_id, postcond_decl) = {
244            // Set up the postcondition `let` statement.
245            let (checker_pat, check_hir_id) = self.pat_ident_binding_mode_mut(
246                postcond_span,
247                check_ident,
248                rustc_hir::BindingMode::NONE,
249            );
250            (
251                check_hir_id,
252                self.stmt_let_pat(
253                    None,
254                    postcond_span,
255                    Some(contract_check),
256                    self.arena.alloc(checker_pat),
257                    rustc_hir::LocalSource::Contract,
258                ),
259            )
260        };
261
262        // Install contract_ensures so we will intercept `return` statements,
263        // then lower the body.
264        self.contract_ensures = Some((postcond_span, check_ident, check_hir_id));
265        let body = self.arena.alloc(body(self));
266
267        // Finally, inject an ensures check on the implicit return of the body.
268        let body = self.inject_ensures_check(body, postcond_span, check_ident, check_hir_id);
269
270        // Flatten the body into precond, then postcond, then wrapped body.
271        let wrapped_body = self.block_all(
272            body.span,
273            self.arena.alloc_from_iter([postcond_decl].into_iter()),
274            Some(body),
275        );
276        wrapped_body
277    }
278
279    /// Create an `ExprKind::Ret` that is optionally wrapped by a call to check
280    /// a contract ensures clause, if it exists.
281    pub(super) fn checked_return(
282        &mut self,
283        opt_expr: Option<&'hir rustc_hir::Expr<'hir>>,
284    ) -> rustc_hir::ExprKind<'hir> {
285        let checked_ret =
286            if let Some((check_span, check_ident, check_hir_id)) = self.contract_ensures {
287                let expr = opt_expr.unwrap_or_else(|| self.expr_unit(check_span));
288                Some(self.inject_ensures_check(expr, check_span, check_ident, check_hir_id))
289            } else {
290                opt_expr
291            };
292        rustc_hir::ExprKind::Ret(checked_ret)
293    }
294
295    /// Wraps an expression with a call to the ensures check before it gets returned.
296    pub(super) fn inject_ensures_check(
297        &mut self,
298        expr: &'hir rustc_hir::Expr<'hir>,
299        span: rustc_span::Span,
300        cond_ident: rustc_span::Ident,
301        cond_hir_id: rustc_hir::HirId,
302    ) -> &'hir rustc_hir::Expr<'hir> {
303        // {
304        //     let ret = { body };
305        //
306        //     if contract_checks {
307        //         contract_check_ensures(__postcond, ret)
308        //     } else {
309        //         ret
310        //     }
311        // }
312        let ret_ident: rustc_span::Ident = rustc_span::Ident::from_str_and_span("__ret", span);
313
314        // Set up the return `let` statement.
315        let (ret_pat, ret_hir_id) =
316            self.pat_ident_binding_mode_mut(span, ret_ident, rustc_hir::BindingMode::NONE);
317
318        let ret_stmt = self.stmt_let_pat(
319            None,
320            span,
321            Some(expr),
322            self.arena.alloc(ret_pat),
323            rustc_hir::LocalSource::Contract,
324        );
325
326        let ret = self.expr_ident(span, ret_ident, ret_hir_id);
327
328        let cond_fn = self.expr_ident(span, cond_ident, cond_hir_id);
329        let contract_check = self.expr_call_lang_item_fn_mut(
330            span,
331            LangItem::ContractCheckEnsures,
332            self.arena.alloc_from_iter([*cond_fn, *ret])arena_vec![self; *cond_fn, *ret],
333        );
334        let contract_check = self.arena.alloc(contract_check);
335        let call_expr = self.block_expr_block(contract_check);
336
337        // same ident can't be used in 2 places, so we create a new one for the
338        // else branch
339        let ret = self.expr_ident(span, ret_ident, ret_hir_id);
340        let ret_block = self.block_expr_block(ret);
341
342        let contracts_enabled: rustc_hir::Expr<'_> =
343            self.expr_bool_literal(span, self.tcx.sess.contract_checks());
344        let contract_check = self.arena.alloc(self.expr(
345            span,
346            rustc_hir::ExprKind::If(
347                self.arena.alloc(contracts_enabled),
348                call_expr,
349                Some(ret_block),
350            ),
351        ));
352
353        let attrs: rustc_ast::AttrVec = {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(self.unreachable_code_attr(span));
    vec
}thin_vec![self.unreachable_code_attr(span)];
354        self.lower_attrs(contract_check.hir_id, &attrs, span, Target::Expression);
355
356        let ret_block = self.block_all(span, self.arena.alloc_from_iter([ret_stmt])arena_vec![self; ret_stmt], Some(contract_check));
357        self.arena.alloc(self.expr_block(self.arena.alloc(ret_block)))
358    }
359}