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