Skip to main content

rustc_ast_lowering/
contract.rs

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