]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs
Rollup merge of #77528 - tamird:avoid-cast-net-parser, r=dtolnay
[rust.git] / src / tools / clippy / clippy_lints / src / utils / qualify_min_const_fn.rs
1 use rustc_hir as hir;
2 use rustc_hir::def_id::DefId;
3 use rustc_middle::mir::*;
4 use rustc_middle::ty::subst::GenericArgKind;
5 use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
6 use rustc_span::symbol::{sym};
7 use rustc_span::Span;
8 use rustc_target::spec::abi::Abi::RustIntrinsic;
9 use std::borrow::Cow;
10
11 type McfResult = Result<(), (Span, Cow<'static, str>)>;
12
13 pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
14     let def_id = body.source.def_id();
15     let mut current = def_id;
16     loop {
17         let predicates = tcx.predicates_of(current);
18         for (predicate, _) in predicates.predicates {
19             match predicate.skip_binders() {
20                 ty::PredicateAtom::RegionOutlives(_)
21                 | ty::PredicateAtom::TypeOutlives(_)
22                 | ty::PredicateAtom::WellFormed(_)
23                 | ty::PredicateAtom::Projection(_)
24                 | ty::PredicateAtom::ConstEvaluatable(..)
25                 | ty::PredicateAtom::ConstEquate(..)
26                 | ty::PredicateAtom::TypeWellFormedFromEnv(..) => continue,
27                 ty::PredicateAtom::ObjectSafe(_) => {
28                     panic!("object safe predicate on function: {:#?}", predicate)
29                 }
30                 ty::PredicateAtom::ClosureKind(..) => {
31                     panic!("closure kind predicate on function: {:#?}", predicate)
32                 }
33                 ty::PredicateAtom::Subtype(_) => {
34                     panic!("subtype predicate on function: {:#?}", predicate)
35                 }
36                 ty::PredicateAtom::Trait(pred, _) => {
37                     if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
38                         continue;
39                     }
40                     match pred.self_ty().kind() {
41                         ty::Param(ref p) => {
42                             let generics = tcx.generics_of(current);
43                             let def = generics.type_param(p, tcx);
44                             let span = tcx.def_span(def.def_id);
45                             return Err((
46                                 span,
47                                 "trait bounds other than `Sized` \
48                                  on const fn parameters are unstable"
49                                     .into(),
50                             ));
51                         }
52                         // other kinds of bounds are either tautologies
53                         // or cause errors in other passes
54                         _ => continue,
55                     }
56                 }
57             }
58         }
59         match predicates.parent {
60             Some(parent) => current = parent,
61             None => break,
62         }
63     }
64
65     for local in &body.local_decls {
66         check_ty(tcx, local.ty, local.source_info.span)?;
67     }
68     // impl trait is gone in MIR, so check the return type manually
69     check_ty(
70         tcx,
71         tcx.fn_sig(def_id).output().skip_binder(),
72         body.local_decls.iter().next().unwrap().source_info.span,
73     )?;
74
75     for bb in body.basic_blocks() {
76         check_terminator(tcx, body, bb.terminator())?;
77         for stmt in &bb.statements {
78             check_statement(tcx, body, def_id, stmt)?;
79         }
80     }
81     Ok(())
82 }
83
84 fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
85     for arg in ty.walk() {
86         let ty = match arg.unpack() {
87             GenericArgKind::Type(ty) => ty,
88
89             // No constraints on lifetimes or constants, except potentially
90             // constants' types, but `walk` will get to them as well.
91             GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
92         };
93
94         match ty.kind() {
95             ty::Ref(_, _, hir::Mutability::Mut) => {
96                     return Err((span, "mutable references in const fn are unstable".into()));
97             }
98             ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
99             ty::FnPtr(..) => {
100                     return Err((span, "function pointers in const fn are unstable".into()));
101             }
102             ty::Dynamic(preds, _) => {
103                 for pred in preds.iter() {
104                     match pred.skip_binder() {
105                         ty::ExistentialPredicate::AutoTrait(_)
106                         | ty::ExistentialPredicate::Projection(_) => {
107                             return Err((
108                                 span,
109                                 "trait bounds other than `Sized` \
110                                  on const fn parameters are unstable"
111                                     .into(),
112                             ));
113                         }
114                         ty::ExistentialPredicate::Trait(trait_ref) => {
115                             if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
116                                 return Err((
117                                     span,
118                                     "trait bounds other than `Sized` \
119                                      on const fn parameters are unstable"
120                                         .into(),
121                                 ));
122                             }
123                         }
124                     }
125                 }
126             }
127             _ => {}
128         }
129     }
130     Ok(())
131 }
132
133 fn check_rvalue(
134     tcx: TyCtxt<'tcx>,
135     body: &Body<'tcx>,
136     def_id: DefId,
137     rvalue: &Rvalue<'tcx>,
138     span: Span,
139 ) -> McfResult {
140     match rvalue {
141         Rvalue::ThreadLocalRef(_) => {
142             Err((span, "cannot access thread local storage in const fn".into()))
143         }
144         Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => {
145             check_operand(tcx, operand, span, body)
146         }
147         Rvalue::Len(place)
148         | Rvalue::Discriminant(place)
149         | Rvalue::Ref(_, _, place)
150         | Rvalue::AddressOf(_, place) => check_place(tcx, *place, span,  body),
151         Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
152             use rustc_middle::ty::cast::CastTy;
153             let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
154             let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
155             match (cast_in, cast_out) {
156                 (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
157                     Err((span, "casting pointers to ints is unstable in const fn".into()))
158                 }
159                 _ => check_operand(tcx, operand, span, body),
160             }
161         }
162         Rvalue::Cast(
163             CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer),
164             operand,
165             _,
166         ) => check_operand(tcx, operand, span, body),
167         Rvalue::Cast(
168             CastKind::Pointer(
169                 PointerCast::UnsafeFnPointer
170                 | PointerCast::ClosureFnPointer(_)
171                 | PointerCast::ReifyFnPointer,
172             ),
173             _,
174             _,
175         ) => Err((span, "function pointer casts are not allowed in const fn".into())),
176         Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
177             let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
178                 deref_ty.ty
179             } else {
180                 // We cannot allow this for now.
181                 return Err((
182                     span,
183                     "unsizing casts are only allowed for references right now".into(),
184                 ));
185             };
186             let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
187             if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
188                 check_operand(tcx, op, span, body)?;
189                 // Casting/coercing things to slices is fine.
190                 Ok(())
191             } else {
192                 // We just can't allow trait objects until we have figured out trait method calls.
193                 Err((span, "unsizing casts are not allowed in const fn".into()))
194             }
195         }
196         // binops are fine on integers
197         Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
198             check_operand(tcx, lhs, span, body)?;
199             check_operand(tcx, rhs, span, body)?;
200             let ty = lhs.ty(body, tcx);
201             if ty.is_integral() || ty.is_bool() || ty.is_char() {
202                 Ok(())
203             } else {
204                 Err((span, "only int, `bool` and `char` operations are stable in const fn".into()))
205             }
206         }
207         Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
208         Rvalue::NullaryOp(NullOp::Box, _) => {
209             Err((span, "heap allocations are not allowed in const fn".into()))
210         }
211         Rvalue::UnaryOp(_, operand) => {
212             let ty = operand.ty(body, tcx);
213             if ty.is_integral() || ty.is_bool() {
214                 check_operand(tcx, operand, span, body)
215             } else {
216                 Err((span, "only int and `bool` operations are stable in const fn".into()))
217             }
218         }
219         Rvalue::Aggregate(_, operands) => {
220             for operand in operands {
221                 check_operand(tcx, operand, span, body)?;
222             }
223             Ok(())
224         }
225     }
226 }
227
228 fn check_statement(
229     tcx: TyCtxt<'tcx>,
230     body: &Body<'tcx>,
231     def_id: DefId,
232     statement: &Statement<'tcx>,
233 ) -> McfResult {
234     let span = statement.source_info.span;
235     match &statement.kind {
236         StatementKind::Assign(box (place, rval)) => {
237             check_place(tcx, *place, span,  body)?;
238             check_rvalue(tcx, body, def_id, rval, span)
239         }
240
241         StatementKind::FakeRead(_, place) => check_place(tcx, **place, span, body),
242
243         // just an assignment
244         StatementKind::SetDiscriminant { place, .. } => {
245             check_place(tcx, **place, span,  body)
246         }
247
248         StatementKind::LlvmInlineAsm { .. } => {
249             Err((span, "cannot use inline assembly in const fn".into()))
250         }
251
252         // These are all NOPs
253         StatementKind::StorageLive(_)
254         | StatementKind::StorageDead(_)
255         | StatementKind::Retag { .. }
256         | StatementKind::AscribeUserType(..)
257         | StatementKind::Coverage(..)
258         | StatementKind::Nop => Ok(()),
259     }
260 }
261
262 fn check_operand(
263     tcx: TyCtxt<'tcx>,
264     operand: &Operand<'tcx>,
265     span: Span,
266     body: &Body<'tcx>,
267 ) -> McfResult {
268     match operand {
269         Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
270         Operand::Constant(c) => match c.check_static_ptr(tcx) {
271             Some(_) => Err((span, "cannot access `static` items in const fn".into())),
272             None => Ok(()),
273         },
274     }
275 }
276
277 fn check_place(
278     tcx: TyCtxt<'tcx>,
279     place: Place<'tcx>,
280     span: Span,
281     body: &Body<'tcx>,
282 ) -> McfResult {
283     let mut cursor = place.projection.as_ref();
284     while let &[ref proj_base @ .., elem] = cursor {
285         cursor = proj_base;
286         match elem {
287             ProjectionElem::Field(..) => {
288                 let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty;
289                 if let Some(def) = base_ty.ty_adt_def() {
290                     // No union field accesses in `const fn`
291                     if def.is_union() {
292                             return Err((span, "accessing union fields is unstable".into()));
293                     }
294                 }
295             }
296             ProjectionElem::ConstantIndex { .. }
297             | ProjectionElem::Downcast(..)
298             | ProjectionElem::Subslice { .. }
299             | ProjectionElem::Deref
300             | ProjectionElem::Index(_) => {}
301         }
302     }
303
304     Ok(())
305 }
306
307 fn check_terminator(
308     tcx: TyCtxt<'tcx>,
309     body: &'a Body<'tcx>,
310     terminator: &Terminator<'tcx>,
311 ) -> McfResult {
312     let span = terminator.source_info.span;
313     match &terminator.kind {
314         TerminatorKind::FalseEdge { .. }
315         | TerminatorKind::FalseUnwind { .. }
316         | TerminatorKind::Goto { .. }
317         | TerminatorKind::Return
318         | TerminatorKind::Resume
319         | TerminatorKind::Unreachable => Ok(()),
320
321         TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span,  body),
322         TerminatorKind::DropAndReplace { place, value, .. } => {
323             check_place(tcx, *place, span,  body)?;
324             check_operand(tcx, value, span, body)
325         }
326
327         TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => {
328             check_operand(tcx, discr, span, body)
329         }
330
331         TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
332         TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
333             Err((span, "const fn generators are unstable".into()))
334         }
335
336         TerminatorKind::Call {
337             func,
338             args,
339             from_hir_call: _,
340             destination: _,
341             cleanup: _,
342             fn_span: _,
343         } => {
344             let fn_ty = func.ty(body, tcx);
345             if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
346                 if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id)
347                 {
348                     return Err((
349                         span,
350                         format!(
351                             "can only call other `const fn` within a `const fn`, \
352                              but `{:?}` is not stable as `const fn`",
353                             func,
354                         )
355                         .into(),
356                     ));
357                 }
358
359                 // HACK: This is to "unstabilize" the `transmute` intrinsic
360                 // within const fns. `transmute` is allowed in all other const contexts.
361                 // This won't really scale to more intrinsics or functions. Let's allow const
362                 // transmutes in const fn before we add more hacks to this.
363                 if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic
364                     && tcx.item_name(fn_def_id) == sym::transmute
365                 {
366                     return Err((
367                         span,
368                         "can only call `transmute` from const items, not `const fn`".into(),
369                     ));
370                 }
371
372                 check_operand(tcx, func, span, body)?;
373
374                 for arg in args {
375                     check_operand(tcx, arg, span, 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, body)
385         }
386
387         TerminatorKind::InlineAsm { .. } => {
388             Err((span, "cannot use inline assembly in const fn".into()))
389         }
390     }
391 }