From 1b843896c8b0a994835e933e67ca4e3de042ce9f Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 26 Sep 2020 16:08:24 +0200 Subject: Move `qualify_min_const_fn` out of rustc into clippy --- compiler/rustc_mir/src/transform/mod.rs | 1 - .../src/transform/qualify_min_const_fn.rs | 462 --------------------- src/tools/clippy/clippy_lints/src/lib.rs | 1 + .../clippy_lints/src/missing_const_for_fn.rs | 2 +- src/tools/clippy/clippy_lints/src/utils/mod.rs | 1 + .../clippy_lints/src/utils/qualify_min_const_fn.rs | 462 +++++++++++++++++++++ 6 files changed, 465 insertions(+), 464 deletions(-) delete mode 100644 compiler/rustc_mir/src/transform/qualify_min_const_fn.rs create mode 100644 src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs diff --git a/compiler/rustc_mir/src/transform/mod.rs b/compiler/rustc_mir/src/transform/mod.rs index a622de9a8af..850cafcf898 100644 --- a/compiler/rustc_mir/src/transform/mod.rs +++ b/compiler/rustc_mir/src/transform/mod.rs @@ -36,7 +36,6 @@ pub mod match_branches; pub mod no_landing_pads; pub mod nrvo; pub mod promote_consts; -pub mod qualify_min_const_fn; pub mod remove_noop_landing_pads; pub mod remove_unneeded_drops; pub mod required_consts; diff --git a/compiler/rustc_mir/src/transform/qualify_min_const_fn.rs b/compiler/rustc_mir/src/transform/qualify_min_const_fn.rs deleted file mode 100644 index f15a7f7c2c8..00000000000 --- a/compiler/rustc_mir/src/transform/qualify_min_const_fn.rs +++ /dev/null @@ -1,462 +0,0 @@ -use rustc_hir as hir; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::*; -use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; -use rustc_span::symbol::{sym, Symbol}; -use rustc_span::Span; -use rustc_target::spec::abi::Abi::RustIntrinsic; -use std::borrow::Cow; - -type McfResult = Result<(), (Span, Cow<'static, str>)>; - -pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult { - // Prevent const trait methods from being annotated as `stable`. - if tcx.features().staged_api { - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); - if crate::const_eval::is_parent_const_impl_raw(tcx, hir_id) { - return Err((body.span, "trait methods cannot be stable const fn".into())); - } - } - - let mut current = def_id; - loop { - let predicates = tcx.predicates_of(current); - for (predicate, _) in predicates.predicates { - match predicate.skip_binders() { - ty::PredicateAtom::RegionOutlives(_) - | ty::PredicateAtom::TypeOutlives(_) - | ty::PredicateAtom::WellFormed(_) - | ty::PredicateAtom::Projection(_) - | ty::PredicateAtom::ConstEvaluatable(..) - | ty::PredicateAtom::ConstEquate(..) - | ty::PredicateAtom::TypeWellFormedFromEnv(..) => continue, - ty::PredicateAtom::ObjectSafe(_) => { - bug!("object safe predicate on function: {:#?}", predicate) - } - ty::PredicateAtom::ClosureKind(..) => { - bug!("closure kind predicate on function: {:#?}", predicate) - } - ty::PredicateAtom::Subtype(_) => { - bug!("subtype predicate on function: {:#?}", predicate) - } - ty::PredicateAtom::Trait(pred, constness) => { - if Some(pred.def_id()) == tcx.lang_items().sized_trait() { - continue; - } - match pred.self_ty().kind() { - ty::Param(ref p) => { - // Allow `T: ?const Trait` - if constness == hir::Constness::NotConst - && feature_allowed(tcx, def_id, sym::const_trait_bound_opt_out) - { - continue; - } - - let generics = tcx.generics_of(current); - let def = generics.type_param(p, tcx); - let span = tcx.def_span(def.def_id); - return Err(( - span, - "trait bounds other than `Sized` \ - on const fn parameters are unstable" - .into(), - )); - } - // other kinds of bounds are either tautologies - // or cause errors in other passes - _ => continue, - } - } - } - } - match predicates.parent { - Some(parent) => current = parent, - None => break, - } - } - - for local in &body.local_decls { - check_ty(tcx, local.ty, local.source_info.span, def_id)?; - } - // impl trait is gone in MIR, so check the return type manually - check_ty( - tcx, - tcx.fn_sig(def_id).output().skip_binder(), - body.local_decls.iter().next().unwrap().source_info.span, - def_id, - )?; - - for bb in body.basic_blocks() { - check_terminator(tcx, body, def_id, bb.terminator())?; - for stmt in &bb.statements { - check_statement(tcx, body, def_id, stmt)?; - } - } - Ok(()) -} - -fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> McfResult { - for arg in ty.walk() { - let ty = match arg.unpack() { - GenericArgKind::Type(ty) => ty, - - // No constraints on lifetimes or constants, except potentially - // constants' types, but `walk` will get to them as well. - GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue, - }; - - match ty.kind() { - ty::Ref(_, _, hir::Mutability::Mut) => { - if !feature_allowed(tcx, fn_def_id, sym::const_mut_refs) { - return Err((span, "mutable references in const fn are unstable".into())); - } - } - ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())), - ty::FnPtr(..) => { - if !tcx.const_fn_is_allowed_fn_ptr(fn_def_id) { - return Err((span, "function pointers in const fn are unstable".into())); - } - } - ty::Dynamic(preds, _) => { - for pred in preds.iter() { - match pred.skip_binder() { - ty::ExistentialPredicate::AutoTrait(_) - | ty::ExistentialPredicate::Projection(_) => { - return Err(( - span, - "trait bounds other than `Sized` \ - on const fn parameters are unstable" - .into(), - )); - } - ty::ExistentialPredicate::Trait(trait_ref) => { - if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() { - return Err(( - span, - "trait bounds other than `Sized` \ - on const fn parameters are unstable" - .into(), - )); - } - } - } - } - } - _ => {} - } - } - Ok(()) -} - -fn check_rvalue( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - def_id: DefId, - rvalue: &Rvalue<'tcx>, - span: Span, -) -> McfResult { - match rvalue { - Rvalue::ThreadLocalRef(_) => { - Err((span, "cannot access thread local storage in const fn".into())) - } - Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => { - check_operand(tcx, operand, span, def_id, body) - } - Rvalue::Len(place) - | Rvalue::Discriminant(place) - | Rvalue::Ref(_, _, place) - | Rvalue::AddressOf(_, place) => check_place(tcx, *place, span, def_id, body), - Rvalue::Cast(CastKind::Misc, operand, cast_ty) => { - use rustc_middle::ty::cast::CastTy; - let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast"); - let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); - match (cast_in, cast_out) { - (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => { - Err((span, "casting pointers to ints is unstable in const fn".into())) - } - _ => check_operand(tcx, operand, span, def_id, body), - } - } - Rvalue::Cast( - CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), - operand, - _, - ) => check_operand(tcx, operand, span, def_id, body), - Rvalue::Cast( - CastKind::Pointer( - PointerCast::UnsafeFnPointer - | PointerCast::ClosureFnPointer(_) - | PointerCast::ReifyFnPointer, - ), - _, - _, - ) => Err((span, "function pointer casts are not allowed in const fn".into())), - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => { - let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) { - deref_ty.ty - } else { - // We cannot allow this for now. - return Err(( - span, - "unsizing casts are only allowed for references right now".into(), - )); - }; - let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); - if let ty::Slice(_) | ty::Str = unsized_ty.kind() { - check_operand(tcx, op, span, def_id, body)?; - // Casting/coercing things to slices is fine. - Ok(()) - } else { - // We just can't allow trait objects until we have figured out trait method calls. - Err((span, "unsizing casts are not allowed in const fn".into())) - } - } - // binops are fine on integers - Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => { - check_operand(tcx, lhs, span, def_id, body)?; - check_operand(tcx, rhs, span, def_id, body)?; - let ty = lhs.ty(body, tcx); - if ty.is_integral() || ty.is_bool() || ty.is_char() { - Ok(()) - } else { - Err((span, "only int, `bool` and `char` operations are stable in const fn".into())) - } - } - Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()), - Rvalue::NullaryOp(NullOp::Box, _) => { - Err((span, "heap allocations are not allowed in const fn".into())) - } - Rvalue::UnaryOp(_, operand) => { - let ty = operand.ty(body, tcx); - if ty.is_integral() || ty.is_bool() { - check_operand(tcx, operand, span, def_id, body) - } else { - Err((span, "only int and `bool` operations are stable in const fn".into())) - } - } - Rvalue::Aggregate(_, operands) => { - for operand in operands { - check_operand(tcx, operand, span, def_id, body)?; - } - Ok(()) - } - } -} - -fn check_statement( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - def_id: DefId, - statement: &Statement<'tcx>, -) -> McfResult { - let span = statement.source_info.span; - match &statement.kind { - StatementKind::Assign(box (place, rval)) => { - check_place(tcx, *place, span, def_id, body)?; - check_rvalue(tcx, body, def_id, rval, span) - } - - StatementKind::FakeRead(_, place) => check_place(tcx, **place, span, def_id, body), - - // just an assignment - StatementKind::SetDiscriminant { place, .. } => { - check_place(tcx, **place, span, def_id, body) - } - - StatementKind::LlvmInlineAsm { .. } => { - Err((span, "cannot use inline assembly in const fn".into())) - } - - // These are all NOPs - StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) - | StatementKind::Retag { .. } - | StatementKind::AscribeUserType(..) - | StatementKind::Coverage(..) - | StatementKind::Nop => Ok(()), - } -} - -fn check_operand( - tcx: TyCtxt<'tcx>, - operand: &Operand<'tcx>, - span: Span, - def_id: DefId, - body: &Body<'tcx>, -) -> McfResult { - match operand { - Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, def_id, body), - Operand::Constant(c) => match c.check_static_ptr(tcx) { - Some(_) => Err((span, "cannot access `static` items in const fn".into())), - None => Ok(()), - }, - } -} - -fn check_place( - tcx: TyCtxt<'tcx>, - place: Place<'tcx>, - span: Span, - def_id: DefId, - body: &Body<'tcx>, -) -> McfResult { - let mut cursor = place.projection.as_ref(); - while let &[ref proj_base @ .., elem] = cursor { - cursor = proj_base; - match elem { - ProjectionElem::Field(..) => { - let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty; - if let Some(def) = base_ty.ty_adt_def() { - // No union field accesses in `const fn` - if def.is_union() { - if !feature_allowed(tcx, def_id, sym::const_fn_union) { - return Err((span, "accessing union fields is unstable".into())); - } - } - } - } - ProjectionElem::ConstantIndex { .. } - | ProjectionElem::Downcast(..) - | ProjectionElem::Subslice { .. } - | ProjectionElem::Deref - | ProjectionElem::Index(_) => {} - } - } - - Ok(()) -} - -/// Returns `true` if the given feature gate is allowed within the function with the given `DefId`. -fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { - // All features require that the corresponding gate be enabled, - // even if the function has `#[allow_internal_unstable(the_gate)]`. - if !tcx.features().enabled(feature_gate) { - return false; - } - - // If this crate is not using stability attributes, or this function is not claiming to be a - // stable `const fn`, that is all that is required. - if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { - return true; - } - - // However, we cannot allow stable `const fn`s to use unstable features without an explicit - // opt-in via `allow_internal_unstable`. - super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) -} - -/// Returns `true` if the given library feature gate is allowed within the function with the given `DefId`. -pub fn lib_feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { - // All features require that the corresponding gate be enabled, - // even if the function has `#[allow_internal_unstable(the_gate)]`. - if !tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == feature_gate) { - return false; - } - - // If this crate is not using stability attributes, or this function is not claiming to be a - // stable `const fn`, that is all that is required. - if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { - return true; - } - - // However, we cannot allow stable `const fn`s to use unstable features without an explicit - // opt-in via `allow_internal_unstable`. - super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) -} - -fn check_terminator( - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - def_id: DefId, - terminator: &Terminator<'tcx>, -) -> McfResult { - let span = terminator.source_info.span; - match &terminator.kind { - TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } - | TerminatorKind::Goto { .. } - | TerminatorKind::Return - | TerminatorKind::Resume - | TerminatorKind::Unreachable => Ok(()), - - TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, def_id, body), - TerminatorKind::DropAndReplace { place, value, .. } => { - check_place(tcx, *place, span, def_id, body)?; - check_operand(tcx, value, span, def_id, body) - } - - TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => { - check_operand(tcx, discr, span, def_id, body) - } - - TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())), - TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => { - Err((span, "const fn generators are unstable".into())) - } - - TerminatorKind::Call { - func, - args, - from_hir_call: _, - destination: _, - cleanup: _, - fn_span: _, - } => { - let fn_ty = func.ty(body, tcx); - if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { - // Allow unstable const if we opt in by using #[allow_internal_unstable] - // on function or macro declaration. - if !crate::const_eval::is_min_const_fn(tcx, fn_def_id) - && !crate::const_eval::is_unstable_const_fn(tcx, fn_def_id) - .map(|feature| { - span.allows_unstable(feature) - || lib_feature_allowed(tcx, def_id, feature) - }) - .unwrap_or(false) - { - return Err(( - span, - format!( - "can only call other `const fn` within a `const fn`, \ - but `{:?}` is not stable as `const fn`", - func, - ) - .into(), - )); - } - - // HACK: This is to "unstabilize" the `transmute` intrinsic - // within const fns. `transmute` is allowed in all other const contexts. - // This won't really scale to more intrinsics or functions. Let's allow const - // transmutes in const fn before we add more hacks to this. - if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic - && tcx.item_name(fn_def_id) == sym::transmute - && !feature_allowed(tcx, def_id, sym::const_fn_transmute) - { - return Err(( - span, - "can only call `transmute` from const items, not `const fn`".into(), - )); - } - - check_operand(tcx, func, span, fn_def_id, body)?; - - for arg in args { - check_operand(tcx, arg, span, fn_def_id, body)?; - } - Ok(()) - } else { - Err((span, "can only call other const fns within const fn".into())) - } - } - - TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => { - check_operand(tcx, cond, span, def_id, body) - } - - TerminatorKind::InlineAsm { .. } => { - Err((span, "cannot use inline assembly in const fn".into())) - } - } -} diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 58112ac8da5..c3ff34e6e1e 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -6,6 +6,7 @@ #![feature(concat_idents)] #![feature(crate_visibility_modifier)] #![feature(drain_filter)] +#![feature(in_band_lifetimes)] #![feature(or_patterns)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs index 1ad184dfc46..e5f7cc51111 100644 --- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs +++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs @@ -4,7 +4,7 @@ use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; -use rustc_mir::transform::qualify_min_const_fn::is_min_const_fn; +use crate::utils::qualify_min_const_fn::is_min_const_fn; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; use rustc_typeck::hir_ty_to_ty; diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs index ea52741b7cc..96d9905027b 100644 --- a/src/tools/clippy/clippy_lints/src/utils/mod.rs +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -20,6 +20,7 @@ pub mod paths; pub mod ptr; pub mod sugg; pub mod usage; +pub mod qualify_min_const_fn; pub use self::attrs::*; pub use self::diagnostics::*; diff --git a/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs b/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs new file mode 100644 index 00000000000..9fa9b0341b1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs @@ -0,0 +1,462 @@ +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_middle::mir::*; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::Span; +use rustc_target::spec::abi::Abi::RustIntrinsic; +use std::borrow::Cow; + +type McfResult = Result<(), (Span, Cow<'static, str>)>; + +pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult { + // Prevent const trait methods from being annotated as `stable`. + if tcx.features().staged_api { + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); + if rustc_mir::const_eval::is_parent_const_impl_raw(tcx, hir_id) { + return Err((body.span, "trait methods cannot be stable const fn".into())); + } + } + + let mut current = def_id; + loop { + let predicates = tcx.predicates_of(current); + for (predicate, _) in predicates.predicates { + match predicate.skip_binders() { + ty::PredicateAtom::RegionOutlives(_) + | ty::PredicateAtom::TypeOutlives(_) + | ty::PredicateAtom::WellFormed(_) + | ty::PredicateAtom::Projection(_) + | ty::PredicateAtom::ConstEvaluatable(..) + | ty::PredicateAtom::ConstEquate(..) + | ty::PredicateAtom::TypeWellFormedFromEnv(..) => continue, + ty::PredicateAtom::ObjectSafe(_) => { + panic!("object safe predicate on function: {:#?}", predicate) + } + ty::PredicateAtom::ClosureKind(..) => { + panic!("closure kind predicate on function: {:#?}", predicate) + } + ty::PredicateAtom::Subtype(_) => { + panic!("subtype predicate on function: {:#?}", predicate) + } + ty::PredicateAtom::Trait(pred, constness) => { + if Some(pred.def_id()) == tcx.lang_items().sized_trait() { + continue; + } + match pred.self_ty().kind() { + ty::Param(ref p) => { + // Allow `T: ?const Trait` + if constness == hir::Constness::NotConst + && feature_allowed(tcx, def_id, sym::const_trait_bound_opt_out) + { + continue; + } + + let generics = tcx.generics_of(current); + let def = generics.type_param(p, tcx); + let span = tcx.def_span(def.def_id); + return Err(( + span, + "trait bounds other than `Sized` \ + on const fn parameters are unstable" + .into(), + )); + } + // other kinds of bounds are either tautologies + // or cause errors in other passes + _ => continue, + } + } + } + } + match predicates.parent { + Some(parent) => current = parent, + None => break, + } + } + + for local in &body.local_decls { + check_ty(tcx, local.ty, local.source_info.span, def_id)?; + } + // impl trait is gone in MIR, so check the return type manually + check_ty( + tcx, + tcx.fn_sig(def_id).output().skip_binder(), + body.local_decls.iter().next().unwrap().source_info.span, + def_id, + )?; + + for bb in body.basic_blocks() { + check_terminator(tcx, body, def_id, bb.terminator())?; + for stmt in &bb.statements { + check_statement(tcx, body, def_id, stmt)?; + } + } + Ok(()) +} + +fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> McfResult { + for arg in ty.walk() { + let ty = match arg.unpack() { + GenericArgKind::Type(ty) => ty, + + // No constraints on lifetimes or constants, except potentially + // constants' types, but `walk` will get to them as well. + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue, + }; + + match ty.kind() { + ty::Ref(_, _, hir::Mutability::Mut) => { + if !feature_allowed(tcx, fn_def_id, sym::const_mut_refs) { + return Err((span, "mutable references in const fn are unstable".into())); + } + } + ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())), + ty::FnPtr(..) => { + if !tcx.const_fn_is_allowed_fn_ptr(fn_def_id) { + return Err((span, "function pointers in const fn are unstable".into())); + } + } + ty::Dynamic(preds, _) => { + for pred in preds.iter() { + match pred.skip_binder() { + ty::ExistentialPredicate::AutoTrait(_) + | ty::ExistentialPredicate::Projection(_) => { + return Err(( + span, + "trait bounds other than `Sized` \ + on const fn parameters are unstable" + .into(), + )); + } + ty::ExistentialPredicate::Trait(trait_ref) => { + if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() { + return Err(( + span, + "trait bounds other than `Sized` \ + on const fn parameters are unstable" + .into(), + )); + } + } + } + } + } + _ => {} + } + } + Ok(()) +} + +fn check_rvalue( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + def_id: DefId, + rvalue: &Rvalue<'tcx>, + span: Span, +) -> McfResult { + match rvalue { + Rvalue::ThreadLocalRef(_) => { + Err((span, "cannot access thread local storage in const fn".into())) + } + Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => { + check_operand(tcx, operand, span, def_id, body) + } + Rvalue::Len(place) + | Rvalue::Discriminant(place) + | Rvalue::Ref(_, _, place) + | Rvalue::AddressOf(_, place) => check_place(tcx, *place, span, def_id, body), + Rvalue::Cast(CastKind::Misc, operand, cast_ty) => { + use rustc_middle::ty::cast::CastTy; + let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast"); + let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); + match (cast_in, cast_out) { + (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => { + Err((span, "casting pointers to ints is unstable in const fn".into())) + } + _ => check_operand(tcx, operand, span, def_id, body), + } + } + Rvalue::Cast( + CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), + operand, + _, + ) => check_operand(tcx, operand, span, def_id, body), + Rvalue::Cast( + CastKind::Pointer( + PointerCast::UnsafeFnPointer + | PointerCast::ClosureFnPointer(_) + | PointerCast::ReifyFnPointer, + ), + _, + _, + ) => Err((span, "function pointer casts are not allowed in const fn".into())), + Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => { + let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) { + deref_ty.ty + } else { + // We cannot allow this for now. + return Err(( + span, + "unsizing casts are only allowed for references right now".into(), + )); + }; + let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); + if let ty::Slice(_) | ty::Str = unsized_ty.kind() { + check_operand(tcx, op, span, def_id, body)?; + // Casting/coercing things to slices is fine. + Ok(()) + } else { + // We just can't allow trait objects until we have figured out trait method calls. + Err((span, "unsizing casts are not allowed in const fn".into())) + } + } + // binops are fine on integers + Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => { + check_operand(tcx, lhs, span, def_id, body)?; + check_operand(tcx, rhs, span, def_id, body)?; + let ty = lhs.ty(body, tcx); + if ty.is_integral() || ty.is_bool() || ty.is_char() { + Ok(()) + } else { + Err((span, "only int, `bool` and `char` operations are stable in const fn".into())) + } + } + Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()), + Rvalue::NullaryOp(NullOp::Box, _) => { + Err((span, "heap allocations are not allowed in const fn".into())) + } + Rvalue::UnaryOp(_, operand) => { + let ty = operand.ty(body, tcx); + if ty.is_integral() || ty.is_bool() { + check_operand(tcx, operand, span, def_id, body) + } else { + Err((span, "only int and `bool` operations are stable in const fn".into())) + } + } + Rvalue::Aggregate(_, operands) => { + for operand in operands { + check_operand(tcx, operand, span, def_id, body)?; + } + Ok(()) + } + } +} + +fn check_statement( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + def_id: DefId, + statement: &Statement<'tcx>, +) -> McfResult { + let span = statement.source_info.span; + match &statement.kind { + StatementKind::Assign(box (place, rval)) => { + check_place(tcx, *place, span, def_id, body)?; + check_rvalue(tcx, body, def_id, rval, span) + } + + StatementKind::FakeRead(_, place) => check_place(tcx, **place, span, def_id, body), + + // just an assignment + StatementKind::SetDiscriminant { place, .. } => { + check_place(tcx, **place, span, def_id, body) + } + + StatementKind::LlvmInlineAsm { .. } => { + Err((span, "cannot use inline assembly in const fn".into())) + } + + // These are all NOPs + StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Retag { .. } + | StatementKind::AscribeUserType(..) + | StatementKind::Coverage(..) + | StatementKind::Nop => Ok(()), + } +} + +fn check_operand( + tcx: TyCtxt<'tcx>, + operand: &Operand<'tcx>, + span: Span, + def_id: DefId, + body: &Body<'tcx>, +) -> McfResult { + match operand { + Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, def_id, body), + Operand::Constant(c) => match c.check_static_ptr(tcx) { + Some(_) => Err((span, "cannot access `static` items in const fn".into())), + None => Ok(()), + }, + } +} + +fn check_place( + tcx: TyCtxt<'tcx>, + place: Place<'tcx>, + span: Span, + def_id: DefId, + body: &Body<'tcx>, +) -> McfResult { + let mut cursor = place.projection.as_ref(); + while let &[ref proj_base @ .., elem] = cursor { + cursor = proj_base; + match elem { + ProjectionElem::Field(..) => { + let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty; + if let Some(def) = base_ty.ty_adt_def() { + // No union field accesses in `const fn` + if def.is_union() { + if !feature_allowed(tcx, def_id, sym::const_fn_union) { + return Err((span, "accessing union fields is unstable".into())); + } + } + } + } + ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Downcast(..) + | ProjectionElem::Subslice { .. } + | ProjectionElem::Deref + | ProjectionElem::Index(_) => {} + } + } + + Ok(()) +} + +/// Returns `true` if the given feature gate is allowed within the function with the given `DefId`. +fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { + // All features require that the corresponding gate be enabled, + // even if the function has `#[allow_internal_unstable(the_gate)]`. + if !tcx.features().enabled(feature_gate) { + return false; + } + + // If this crate is not using stability attributes, or this function is not claiming to be a + // stable `const fn`, that is all that is required. + if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { + return true; + } + + // However, we cannot allow stable `const fn`s to use unstable features without an explicit + // opt-in via `allow_internal_unstable`. + rustc_mir::transform::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) +} + +/// Returns `true` if the given library feature gate is allowed within the function with the given `DefId`. +pub fn lib_feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { + // All features require that the corresponding gate be enabled, + // even if the function has `#[allow_internal_unstable(the_gate)]`. + if !tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == feature_gate) { + return false; + } + + // If this crate is not using stability attributes, or this function is not claiming to be a + // stable `const fn`, that is all that is required. + if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { + return true; + } + + // However, we cannot allow stable `const fn`s to use unstable features without an explicit + // opt-in via `allow_internal_unstable`. + rustc_mir::transform::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) +} + +fn check_terminator( + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + def_id: DefId, + terminator: &Terminator<'tcx>, +) -> McfResult { + let span = terminator.source_info.span; + match &terminator.kind { + TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::Goto { .. } + | TerminatorKind::Return + | TerminatorKind::Resume + | TerminatorKind::Unreachable => Ok(()), + + TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, def_id, body), + TerminatorKind::DropAndReplace { place, value, .. } => { + check_place(tcx, *place, span, def_id, body)?; + check_operand(tcx, value, span, def_id, body) + } + + TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => { + check_operand(tcx, discr, span, def_id, body) + } + + TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())), + TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => { + Err((span, "const fn generators are unstable".into())) + } + + TerminatorKind::Call { + func, + args, + from_hir_call: _, + destination: _, + cleanup: _, + fn_span: _, + } => { + let fn_ty = func.ty(body, tcx); + if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { + // Allow unstable const if we opt in by using #[allow_internal_unstable] + // on function or macro declaration. + if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) + && !rustc_mir::const_eval::is_unstable_const_fn(tcx, fn_def_id) + .map(|feature| { + span.allows_unstable(feature) + || lib_feature_allowed(tcx, def_id, feature) + }) + .unwrap_or(false) + { + return Err(( + span, + format!( + "can only call other `const fn` within a `const fn`, \ + but `{:?}` is not stable as `const fn`", + func, + ) + .into(), + )); + } + + // HACK: This is to "unstabilize" the `transmute` intrinsic + // within const fns. `transmute` is allowed in all other const contexts. + // This won't really scale to more intrinsics or functions. Let's allow const + // transmutes in const fn before we add more hacks to this. + if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic + && tcx.item_name(fn_def_id) == sym::transmute + && !feature_allowed(tcx, def_id, sym::const_fn_transmute) + { + return Err(( + span, + "can only call `transmute` from const items, not `const fn`".into(), + )); + } + + check_operand(tcx, func, span, fn_def_id, body)?; + + for arg in args { + check_operand(tcx, arg, span, fn_def_id, body)?; + } + Ok(()) + } else { + Err((span, "can only call other const fns within const fn".into())) + } + } + + TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => { + check_operand(tcx, cond, span, def_id, body) + } + + TerminatorKind::InlineAsm { .. } => { + Err((span, "cannot use inline assembly in const fn".into())) + } + } +} -- cgit 1.4.1-3-g733a5 From 5d359db9cf003ac05e989460e202137a0afa5cce Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 26 Sep 2020 16:23:56 +0200 Subject: Remove all unstable feature support in the `missing_const_for_fn` lint --- .../clippy_lints/src/utils/qualify_min_const_fn.rs | 122 +++++---------------- 1 file changed, 25 insertions(+), 97 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs b/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs index 9fa9b0341b1..6809b1fa88d 100644 --- a/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_lints/src/utils/qualify_min_const_fn.rs @@ -3,7 +3,7 @@ use rustc_hir::def_id::DefId; use rustc_middle::mir::*; use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::{sym}; use rustc_span::Span; use rustc_target::spec::abi::Abi::RustIntrinsic; use std::borrow::Cow; @@ -11,14 +11,6 @@ use std::borrow::Cow; type McfResult = Result<(), (Span, Cow<'static, str>)>; pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult { - // Prevent const trait methods from being annotated as `stable`. - if tcx.features().staged_api { - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); - if rustc_mir::const_eval::is_parent_const_impl_raw(tcx, hir_id) { - return Err((body.span, "trait methods cannot be stable const fn".into())); - } - } - let mut current = def_id; loop { let predicates = tcx.predicates_of(current); @@ -40,19 +32,12 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) - ty::PredicateAtom::Subtype(_) => { panic!("subtype predicate on function: {:#?}", predicate) } - ty::PredicateAtom::Trait(pred, constness) => { + ty::PredicateAtom::Trait(pred, _) => { if Some(pred.def_id()) == tcx.lang_items().sized_trait() { continue; } match pred.self_ty().kind() { ty::Param(ref p) => { - // Allow `T: ?const Trait` - if constness == hir::Constness::NotConst - && feature_allowed(tcx, def_id, sym::const_trait_bound_opt_out) - { - continue; - } - let generics = tcx.generics_of(current); let def = generics.type_param(p, tcx); let span = tcx.def_span(def.def_id); @@ -77,18 +62,17 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) - } for local in &body.local_decls { - check_ty(tcx, local.ty, local.source_info.span, def_id)?; + check_ty(tcx, local.ty, local.source_info.span)?; } // impl trait is gone in MIR, so check the return type manually check_ty( tcx, tcx.fn_sig(def_id).output().skip_binder(), body.local_decls.iter().next().unwrap().source_info.span, - def_id, )?; for bb in body.basic_blocks() { - check_terminator(tcx, body, def_id, bb.terminator())?; + check_terminator(tcx, body, bb.terminator())?; for stmt in &bb.statements { check_statement(tcx, body, def_id, stmt)?; } @@ -96,7 +80,7 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) - Ok(()) } -fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> McfResult { +fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult { for arg in ty.walk() { let ty = match arg.unpack() { GenericArgKind::Type(ty) => ty, @@ -108,15 +92,11 @@ fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> Mc match ty.kind() { ty::Ref(_, _, hir::Mutability::Mut) => { - if !feature_allowed(tcx, fn_def_id, sym::const_mut_refs) { return Err((span, "mutable references in const fn are unstable".into())); - } } ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())), ty::FnPtr(..) => { - if !tcx.const_fn_is_allowed_fn_ptr(fn_def_id) { return Err((span, "function pointers in const fn are unstable".into())); - } } ty::Dynamic(preds, _) => { for pred in preds.iter() { @@ -161,12 +141,12 @@ fn check_rvalue( Err((span, "cannot access thread local storage in const fn".into())) } Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => { - check_operand(tcx, operand, span, def_id, body) + check_operand(tcx, operand, span, body) } Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) - | Rvalue::AddressOf(_, place) => check_place(tcx, *place, span, def_id, body), + | Rvalue::AddressOf(_, place) => check_place(tcx, *place, span, body), Rvalue::Cast(CastKind::Misc, operand, cast_ty) => { use rustc_middle::ty::cast::CastTy; let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast"); @@ -175,14 +155,14 @@ fn check_rvalue( (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => { Err((span, "casting pointers to ints is unstable in const fn".into())) } - _ => check_operand(tcx, operand, span, def_id, body), + _ => check_operand(tcx, operand, span, body), } } Rvalue::Cast( CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), operand, _, - ) => check_operand(tcx, operand, span, def_id, body), + ) => check_operand(tcx, operand, span, body), Rvalue::Cast( CastKind::Pointer( PointerCast::UnsafeFnPointer @@ -204,7 +184,7 @@ fn check_rvalue( }; let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); if let ty::Slice(_) | ty::Str = unsized_ty.kind() { - check_operand(tcx, op, span, def_id, body)?; + check_operand(tcx, op, span, body)?; // Casting/coercing things to slices is fine. Ok(()) } else { @@ -214,8 +194,8 @@ fn check_rvalue( } // binops are fine on integers Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => { - check_operand(tcx, lhs, span, def_id, body)?; - check_operand(tcx, rhs, span, def_id, body)?; + check_operand(tcx, lhs, span, body)?; + check_operand(tcx, rhs, span, body)?; let ty = lhs.ty(body, tcx); if ty.is_integral() || ty.is_bool() || ty.is_char() { Ok(()) @@ -230,14 +210,14 @@ fn check_rvalue( Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, tcx); if ty.is_integral() || ty.is_bool() { - check_operand(tcx, operand, span, def_id, body) + check_operand(tcx, operand, span, body) } else { Err((span, "only int and `bool` operations are stable in const fn".into())) } } Rvalue::Aggregate(_, operands) => { for operand in operands { - check_operand(tcx, operand, span, def_id, body)?; + check_operand(tcx, operand, span, body)?; } Ok(()) } @@ -253,15 +233,15 @@ fn check_statement( let span = statement.source_info.span; match &statement.kind { StatementKind::Assign(box (place, rval)) => { - check_place(tcx, *place, span, def_id, body)?; + check_place(tcx, *place, span, body)?; check_rvalue(tcx, body, def_id, rval, span) } - StatementKind::FakeRead(_, place) => check_place(tcx, **place, span, def_id, body), + StatementKind::FakeRead(_, place) => check_place(tcx, **place, span, body), // just an assignment StatementKind::SetDiscriminant { place, .. } => { - check_place(tcx, **place, span, def_id, body) + check_place(tcx, **place, span, body) } StatementKind::LlvmInlineAsm { .. } => { @@ -282,11 +262,10 @@ fn check_operand( tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, - def_id: DefId, body: &Body<'tcx>, ) -> McfResult { match operand { - Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, def_id, body), + Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body), Operand::Constant(c) => match c.check_static_ptr(tcx) { Some(_) => Err((span, "cannot access `static` items in const fn".into())), None => Ok(()), @@ -298,7 +277,6 @@ fn check_place( tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, - def_id: DefId, body: &Body<'tcx>, ) -> McfResult { let mut cursor = place.projection.as_ref(); @@ -310,9 +288,7 @@ fn check_place( if let Some(def) = base_ty.ty_adt_def() { // No union field accesses in `const fn` if def.is_union() { - if !feature_allowed(tcx, def_id, sym::const_fn_union) { return Err((span, "accessing union fields is unstable".into())); - } } } } @@ -327,48 +303,9 @@ fn check_place( Ok(()) } -/// Returns `true` if the given feature gate is allowed within the function with the given `DefId`. -fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { - // All features require that the corresponding gate be enabled, - // even if the function has `#[allow_internal_unstable(the_gate)]`. - if !tcx.features().enabled(feature_gate) { - return false; - } - - // If this crate is not using stability attributes, or this function is not claiming to be a - // stable `const fn`, that is all that is required. - if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { - return true; - } - - // However, we cannot allow stable `const fn`s to use unstable features without an explicit - // opt-in via `allow_internal_unstable`. - rustc_mir::transform::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) -} - -/// Returns `true` if the given library feature gate is allowed within the function with the given `DefId`. -pub fn lib_feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool { - // All features require that the corresponding gate be enabled, - // even if the function has `#[allow_internal_unstable(the_gate)]`. - if !tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == feature_gate) { - return false; - } - - // If this crate is not using stability attributes, or this function is not claiming to be a - // stable `const fn`, that is all that is required. - if !tcx.features().staged_api || tcx.has_attr(def_id, sym::rustc_const_unstable) { - return true; - } - - // However, we cannot allow stable `const fn`s to use unstable features without an explicit - // opt-in via `allow_internal_unstable`. - rustc_mir::transform::check_consts::allow_internal_unstable(tcx, def_id, feature_gate) -} - fn check_terminator( tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, - def_id: DefId, terminator: &Terminator<'tcx>, ) -> McfResult { let span = terminator.source_info.span; @@ -380,14 +317,14 @@ fn check_terminator( | TerminatorKind::Resume | TerminatorKind::Unreachable => Ok(()), - TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, def_id, body), + TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body), TerminatorKind::DropAndReplace { place, value, .. } => { - check_place(tcx, *place, span, def_id, body)?; - check_operand(tcx, value, span, def_id, body) + check_place(tcx, *place, span, body)?; + check_operand(tcx, value, span, body) } TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => { - check_operand(tcx, discr, span, def_id, body) + check_operand(tcx, discr, span, body) } TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())), @@ -405,15 +342,7 @@ fn check_terminator( } => { let fn_ty = func.ty(body, tcx); if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { - // Allow unstable const if we opt in by using #[allow_internal_unstable] - // on function or macro declaration. if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) - && !rustc_mir::const_eval::is_unstable_const_fn(tcx, fn_def_id) - .map(|feature| { - span.allows_unstable(feature) - || lib_feature_allowed(tcx, def_id, feature) - }) - .unwrap_or(false) { return Err(( span, @@ -432,7 +361,6 @@ fn check_terminator( // transmutes in const fn before we add more hacks to this. if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic && tcx.item_name(fn_def_id) == sym::transmute - && !feature_allowed(tcx, def_id, sym::const_fn_transmute) { return Err(( span, @@ -440,10 +368,10 @@ fn check_terminator( )); } - check_operand(tcx, func, span, fn_def_id, body)?; + check_operand(tcx, func, span, body)?; for arg in args { - check_operand(tcx, arg, span, fn_def_id, body)?; + check_operand(tcx, arg, span, body)?; } Ok(()) } else { @@ -452,7 +380,7 @@ fn check_terminator( } TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => { - check_operand(tcx, cond, span, def_id, body) + check_operand(tcx, cond, span, body) } TerminatorKind::InlineAsm { .. } => { -- cgit 1.4.1-3-g733a5