]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/transform/qualify_min_const_fn.rs
move error allocation test to error.rs
[rust.git] / src / librustc_mir / transform / qualify_min_const_fn.rs
1 use rustc::mir::*;
2 use rustc::ty::{self, adjustment::PointerCast, Predicate, Ty, TyCtxt};
3 use rustc_attr as attr;
4 use rustc_hir as hir;
5 use rustc_hir::def_id::DefId;
6 use rustc_span::symbol::{sym, Symbol};
7 use rustc_span::Span;
8 use std::borrow::Cow;
9
10 type McfResult = Result<(), (Span, Cow<'static, str>)>;
11
12 pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult {
13     // Prevent const trait methods from being annotated as `stable`.
14     if tcx.features().staged_api {
15         let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
16         if crate::const_eval::is_parent_const_impl_raw(tcx, hir_id) {
17             return Err((body.span, "trait methods cannot be stable const fn".into()));
18         }
19     }
20
21     let mut current = def_id;
22     loop {
23         let predicates = tcx.predicates_of(current);
24         for (predicate, _) in predicates.predicates {
25             match predicate {
26                 Predicate::RegionOutlives(_)
27                 | Predicate::TypeOutlives(_)
28                 | Predicate::WellFormed(_)
29                 | Predicate::Projection(_)
30                 | Predicate::ConstEvaluatable(..) => continue,
31                 Predicate::ObjectSafe(_) => {
32                     bug!("object safe predicate on function: {:#?}", predicate)
33                 }
34                 Predicate::ClosureKind(..) => {
35                     bug!("closure kind predicate on function: {:#?}", predicate)
36                 }
37                 Predicate::Subtype(_) => bug!("subtype predicate on function: {:#?}", predicate),
38                 Predicate::Trait(pred, constness) => {
39                     if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
40                         continue;
41                     }
42                     match pred.skip_binder().self_ty().kind {
43                         ty::Param(ref p) => {
44                             // Allow `T: ?const Trait`
45                             if *constness == hir::Constness::NotConst
46                                 && feature_allowed(tcx, def_id, sym::const_trait_bound_opt_out)
47                             {
48                                 continue;
49                             }
50
51                             let generics = tcx.generics_of(current);
52                             let def = generics.type_param(p, tcx);
53                             let span = tcx.def_span(def.def_id);
54                             return Err((
55                                 span,
56                                 "trait bounds other than `Sized` \
57                                  on const fn parameters are unstable"
58                                     .into(),
59                             ));
60                         }
61                         // other kinds of bounds are either tautologies
62                         // or cause errors in other passes
63                         _ => continue,
64                     }
65                 }
66             }
67         }
68         match predicates.parent {
69             Some(parent) => current = parent,
70             None => break,
71         }
72     }
73
74     for local in &body.local_decls {
75         check_ty(tcx, local.ty, local.source_info.span, def_id)?;
76     }
77     // impl trait is gone in MIR, so check the return type manually
78     check_ty(
79         tcx,
80         tcx.fn_sig(def_id).output().skip_binder(),
81         body.local_decls.iter().next().unwrap().source_info.span,
82         def_id,
83     )?;
84
85     for bb in body.basic_blocks() {
86         check_terminator(tcx, body, def_id, bb.terminator())?;
87         for stmt in &bb.statements {
88             check_statement(tcx, body, def_id, stmt)?;
89         }
90     }
91     Ok(())
92 }
93
94 fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> McfResult {
95     for ty in ty.walk() {
96         match ty.kind {
97             ty::Ref(_, _, hir::Mutability::Mut) => {
98                 if !feature_allowed(tcx, fn_def_id, sym::const_mut_refs) {
99                     return Err((span, "mutable references in const fn are unstable".into()));
100                 }
101             }
102             ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
103             ty::FnPtr(..) => {
104                 if !tcx.const_fn_is_allowed_fn_ptr(fn_def_id) {
105                     return Err((span, "function pointers in const fn are unstable".into()));
106                 }
107             }
108             ty::Dynamic(preds, _) => {
109                 for pred in preds.iter() {
110                     match pred.skip_binder() {
111                         ty::ExistentialPredicate::AutoTrait(_)
112                         | ty::ExistentialPredicate::Projection(_) => {
113                             return Err((
114                                 span,
115                                 "trait bounds other than `Sized` \
116                                  on const fn parameters are unstable"
117                                     .into(),
118                             ));
119                         }
120                         ty::ExistentialPredicate::Trait(trait_ref) => {
121                             if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
122                                 return Err((
123                                     span,
124                                     "trait bounds other than `Sized` \
125                                      on const fn parameters are unstable"
126                                         .into(),
127                                 ));
128                             }
129                         }
130                     }
131                 }
132             }
133             _ => {}
134         }
135     }
136     Ok(())
137 }
138
139 fn check_rvalue(
140     tcx: TyCtxt<'tcx>,
141     body: &Body<'tcx>,
142     def_id: DefId,
143     rvalue: &Rvalue<'tcx>,
144     span: Span,
145 ) -> McfResult {
146     match rvalue {
147         Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => {
148             check_operand(tcx, operand, span, def_id, body)
149         }
150         Rvalue::Len(place)
151         | Rvalue::Discriminant(place)
152         | Rvalue::Ref(_, _, place)
153         | Rvalue::AddressOf(_, place) => check_place(tcx, place, span, def_id, body),
154         Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
155             use rustc::ty::cast::CastTy;
156             let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
157             let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
158             match (cast_in, cast_out) {
159                 (CastTy::Ptr(_), CastTy::Int(_)) | (CastTy::FnPtr, CastTy::Int(_)) => {
160                     Err((span, "casting pointers to ints is unstable in const fn".into()))
161                 }
162                 _ => check_operand(tcx, operand, span, def_id, body),
163             }
164         }
165         Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer), operand, _)
166         | Rvalue::Cast(CastKind::Pointer(PointerCast::ArrayToPointer), operand, _) => {
167             check_operand(tcx, operand, span, def_id, body)
168         }
169         Rvalue::Cast(CastKind::Pointer(PointerCast::UnsafeFnPointer), _, _)
170         | Rvalue::Cast(CastKind::Pointer(PointerCast::ClosureFnPointer(_)), _, _)
171         | Rvalue::Cast(CastKind::Pointer(PointerCast::ReifyFnPointer), _, _) => {
172             Err((span, "function pointer casts are not allowed in const fn".into()))
173         }
174         Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), _, _) => {
175             Err((span, "unsizing casts are not allowed in const fn".into()))
176         }
177         // binops are fine on integers
178         Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
179             check_operand(tcx, lhs, span, def_id, body)?;
180             check_operand(tcx, rhs, span, def_id, body)?;
181             let ty = lhs.ty(body, tcx);
182             if ty.is_integral() || ty.is_bool() || ty.is_char() {
183                 Ok(())
184             } else {
185                 Err((span, "only int, `bool` and `char` operations are stable in const fn".into()))
186             }
187         }
188         Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
189         Rvalue::NullaryOp(NullOp::Box, _) => {
190             Err((span, "heap allocations are not allowed in const fn".into()))
191         }
192         Rvalue::UnaryOp(_, operand) => {
193             let ty = operand.ty(body, tcx);
194             if ty.is_integral() || ty.is_bool() {
195                 check_operand(tcx, operand, span, def_id, body)
196             } else {
197                 Err((span, "only int and `bool` operations are stable in const fn".into()))
198             }
199         }
200         Rvalue::Aggregate(_, operands) => {
201             for operand in operands {
202                 check_operand(tcx, operand, span, def_id, body)?;
203             }
204             Ok(())
205         }
206     }
207 }
208
209 fn check_statement(
210     tcx: TyCtxt<'tcx>,
211     body: &Body<'tcx>,
212     def_id: DefId,
213     statement: &Statement<'tcx>,
214 ) -> McfResult {
215     let span = statement.source_info.span;
216     match &statement.kind {
217         StatementKind::Assign(box (place, rval)) => {
218             check_place(tcx, place, span, def_id, body)?;
219             check_rvalue(tcx, body, def_id, rval, span)
220         }
221
222         StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _)
223             if !feature_allowed(tcx, def_id, sym::const_if_match) =>
224         {
225             Err((span, "loops and conditional expressions are not stable in const fn".into()))
226         }
227
228         StatementKind::FakeRead(_, place) => check_place(tcx, place, span, def_id, body),
229
230         // just an assignment
231         StatementKind::SetDiscriminant { place, .. } => check_place(tcx, place, span, def_id, body),
232
233         StatementKind::InlineAsm { .. } => {
234             Err((span, "cannot use inline assembly in const fn".into()))
235         }
236
237         // These are all NOPs
238         StatementKind::StorageLive(_)
239         | StatementKind::StorageDead(_)
240         | StatementKind::Retag { .. }
241         | StatementKind::AscribeUserType(..)
242         | StatementKind::Nop => Ok(()),
243     }
244 }
245
246 fn check_operand(
247     tcx: TyCtxt<'tcx>,
248     operand: &Operand<'tcx>,
249     span: Span,
250     def_id: DefId,
251     body: &Body<'tcx>,
252 ) -> McfResult {
253     match operand {
254         Operand::Move(place) | Operand::Copy(place) => check_place(tcx, place, span, def_id, body),
255         Operand::Constant(c) => match c.check_static_ptr(tcx) {
256             Some(_) => Err((span, "cannot access `static` items in const fn".into())),
257             None => Ok(()),
258         },
259     }
260 }
261
262 fn check_place(
263     tcx: TyCtxt<'tcx>,
264     place: &Place<'tcx>,
265     span: Span,
266     def_id: DefId,
267     body: &Body<'tcx>,
268 ) -> McfResult {
269     let mut cursor = place.projection.as_ref();
270     while let &[ref proj_base @ .., elem] = cursor {
271         cursor = proj_base;
272         match elem {
273             ProjectionElem::Downcast(..) if !feature_allowed(tcx, def_id, sym::const_if_match) => {
274                 return Err((span, "`match` or `if let` in `const fn` is unstable".into()));
275             }
276             ProjectionElem::Downcast(_symbol, _variant_index) => {}
277
278             ProjectionElem::Field(..) => {
279                 let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty;
280                 if let Some(def) = base_ty.ty_adt_def() {
281                     // No union field accesses in `const fn`
282                     if def.is_union() {
283                         if !feature_allowed(tcx, def_id, sym::const_fn_union) {
284                             return Err((span, "accessing union fields is unstable".into()));
285                         }
286                     }
287                 }
288             }
289             ProjectionElem::ConstantIndex { .. }
290             | ProjectionElem::Subslice { .. }
291             | ProjectionElem::Deref
292             | ProjectionElem::Index(_) => {}
293         }
294     }
295
296     Ok(())
297 }
298
299 /// Returns `true` if the given feature gate is allowed within the function with the given `DefId`.
300 fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool {
301     // All features require that the corresponding gate be enabled,
302     // even if the function has `#[allow_internal_unstable(the_gate)]`.
303     if !tcx.features().enabled(feature_gate) {
304         return false;
305     }
306
307     // If this crate is not using stability attributes, or this function is not claiming to be a
308     // stable `const fn`, that is all that is required.
309     if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) {
310         return true;
311     }
312
313     // However, we cannot allow stable `const fn`s to use unstable features without an explicit
314     // opt-in via `allow_internal_unstable`.
315     attr::allow_internal_unstable(&tcx.get_attrs(def_id), &tcx.sess.diagnostic())
316         .map_or(false, |mut features| features.any(|name| name == feature_gate))
317 }
318
319 fn check_terminator(
320     tcx: TyCtxt<'tcx>,
321     body: &'a Body<'tcx>,
322     def_id: DefId,
323     terminator: &Terminator<'tcx>,
324 ) -> McfResult {
325     let span = terminator.source_info.span;
326     match &terminator.kind {
327         TerminatorKind::FalseEdges { .. }
328         | TerminatorKind::FalseUnwind { .. }
329         | TerminatorKind::Goto { .. }
330         | TerminatorKind::Return
331         | TerminatorKind::Resume => Ok(()),
332
333         TerminatorKind::Drop { location, .. } => check_place(tcx, location, span, def_id, body),
334         TerminatorKind::DropAndReplace { location, value, .. } => {
335             check_place(tcx, location, span, def_id, body)?;
336             check_operand(tcx, value, span, def_id, body)
337         }
338
339         TerminatorKind::SwitchInt { .. } if !feature_allowed(tcx, def_id, sym::const_if_match) => {
340             Err((span, "loops and conditional expressions are not stable in const fn".into()))
341         }
342
343         TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => {
344             check_operand(tcx, discr, span, def_id, body)
345         }
346
347         // FIXME(ecstaticmorse): We probably want to allow `Unreachable` unconditionally.
348         TerminatorKind::Unreachable if feature_allowed(tcx, def_id, sym::const_if_match) => Ok(()),
349
350         TerminatorKind::Abort | TerminatorKind::Unreachable => {
351             Err((span, "const fn with unreachable code is not stable".into()))
352         }
353         TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
354             Err((span, "const fn generators are unstable".into()))
355         }
356
357         TerminatorKind::Call { func, args, from_hir_call: _, destination: _, cleanup: _ } => {
358             let fn_ty = func.ty(body, tcx);
359             if let ty::FnDef(def_id, _) = fn_ty.kind {
360                 if !crate::const_eval::is_min_const_fn(tcx, def_id) {
361                     return Err((
362                         span,
363                         format!(
364                             "can only call other `const fn` within a `const fn`, \
365                              but `{:?}` is not stable as `const fn`",
366                             func,
367                         )
368                         .into(),
369                     ));
370                 }
371
372                 check_operand(tcx, func, span, def_id, body)?;
373
374                 for arg in args {
375                     check_operand(tcx, arg, span, def_id, body)?;
376                 }
377                 Ok(())
378             } else {
379                 Err((span, "can only call other const fns within const fn".into()))
380             }
381         }
382
383         TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => {
384             check_operand(tcx, cond, span, def_id, body)
385         }
386     }
387 }