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