X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Flibrustc_lint%2Ftypes.rs;h=de750010ed1e6e1af3a4557d615508191e424809;hb=30a6f57308bf850891a2f4f47b9f1c325e2ac887;hp=46741fcf2ba0c290864bc6aa1b4a39890cd0fc22;hpb=61a3f6701b5e1d8cd87be83c2b6a8322f812c8d8;p=rust.git diff --git a/src/librustc_lint/types.rs b/src/librustc_lint/types.rs index 46741fcf2ba..de750010ed1 100644 --- a/src/librustc_lint/types.rs +++ b/src/librustc_lint/types.rs @@ -15,8 +15,9 @@ use rustc_span::source_map; use rustc_span::symbol::sym; use rustc_span::{Span, DUMMY_SP}; +use rustc_target::abi::Abi; use rustc_target::abi::{Integer, LayoutOf, TagEncoding, VariantIdx, Variants}; -use rustc_target::spec::abi::Abi; +use rustc_target::spec::abi::Abi as SpecAbi; use log::debug; use std::cmp; @@ -168,7 +169,7 @@ fn report_bin_hex_error( repr_str, val, t, actually, t )); if let Some(sugg_ty) = - get_type_suggestion(&cx.tables().node_type(expr.hir_id), val, negative) + get_type_suggestion(&cx.typeck_results().node_type(expr.hir_id), val, negative) { if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { let (sans_suffix, _) = repr_str.split_at(pos); @@ -302,7 +303,7 @@ fn lint_uint_literal<'tcx>( if let Node::Expr(par_e) = cx.tcx.hir().get(parent_id) { match par_e.kind { hir::ExprKind::Cast(..) => { - if let ty::Char = cx.tables().expr_ty(par_e).kind { + if let ty::Char = cx.typeck_results().expr_ty(par_e).kind { cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| { lint.build("only `u8` can be cast into `char`") .span_suggestion( @@ -353,7 +354,7 @@ fn lint_literal<'tcx>( e: &'tcx hir::Expr<'tcx>, lit: &hir::Lit, ) { - match cx.tables().node_type(e.hir_id).kind { + match cx.typeck_results().node_type(e.hir_id).kind { ty::Int(t) => { match lit.node { ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => { @@ -449,7 +450,7 @@ fn check_limits( // Normalize the binop so that the literal is always on the RHS in // the comparison let norm_binop = if swap { rev_binop(binop) } else { binop }; - match cx.tables().node_type(expr.hir_id).kind { + match cx.typeck_results().node_type(expr.hir_id).kind { ty::Int(int_ty) => { let (min, max) = int_ty_range(int_ty); let lit_val: i128 = match lit.kind { @@ -509,14 +510,15 @@ fn is_comparison(binop: hir::BinOp) -> bool { declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]); -enum ImproperCTypesMode { - Declarations, - Definitions, +#[derive(Clone, Copy)] +crate enum CItemKind { + Declaration, + Definition, } struct ImproperCTypesVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - mode: ImproperCTypesMode, + mode: CItemKind, } enum FfiResult<'tcx> { @@ -525,49 +527,94 @@ enum FfiResult<'tcx> { FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option }, } -impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { - /// Is type known to be non-null? - fn ty_is_known_nonnull(&self, ty: Ty<'tcx>) -> bool { - match ty.kind { - ty::FnPtr(_) => true, - ty::Ref(..) => true, - ty::Adt(def, substs) if def.repr.transparent() && !def.is_union() => { - let guaranteed_nonnull_optimization = self - .cx - .tcx - .get_attrs(def.did) - .iter() - .any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed)); - - if guaranteed_nonnull_optimization { - return true; - } +/// Is type known to be non-null? +fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { + let tcx = cx.tcx; + match ty.kind { + ty::FnPtr(_) => true, + ty::Ref(..) => true, + ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, + ty::Adt(def, substs) if def.repr.transparent() && !def.is_union() => { + let guaranteed_nonnull_optimization = tcx + .get_attrs(def.did) + .iter() + .any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed)); - for variant in &def.variants { - if let Some(field) = variant.transparent_newtype_field(self.cx.tcx) { - if self.ty_is_known_nonnull(field.ty(self.cx.tcx, substs)) { - return true; - } + if guaranteed_nonnull_optimization { + return true; + } + for variant in &def.variants { + if let Some(field) = variant.transparent_newtype_field(tcx) { + if ty_is_known_nonnull(cx, field.ty(tcx, substs), mode) { + return true; } } - - false } - _ => false, + + false } + _ => false, } +} +/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. +/// If the type passed in was not scalar, returns None. +fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + let tcx = cx.tcx; + Some(match ty.kind { + ty::Adt(field_def, field_substs) => { + let inner_field_ty = { + let first_non_zst_ty = + field_def.variants.iter().filter_map(|v| v.transparent_newtype_field(tcx)); + debug_assert_eq!( + first_non_zst_ty.clone().count(), + 1, + "Wrong number of fields for transparent type" + ); + first_non_zst_ty + .last() + .expect("No non-zst fields in transparent type.") + .ty(tcx, field_substs) + }; + return get_nullable_type(cx, inner_field_ty); + } + ty::Int(ty) => tcx.mk_mach_int(ty), + ty::Uint(ty) => tcx.mk_mach_uint(ty), + ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut), + // As these types are always non-null, the nullable equivalent of + // Option of these types are their raw pointer counterparts. + ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }), + ty::FnPtr(..) => { + // There is no nullable equivalent for Rust's function pointers -- you + // must use an Option _> to represent it. + ty + } - /// Check if this enum can be safely exported based on the "nullable pointer optimization". - /// Currently restricted to function pointers, references, `core::num::NonZero*`, - /// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. - fn is_repr_nullable_ptr( - &self, - ty: Ty<'tcx>, - ty_def: &'tcx ty::AdtDef, - substs: SubstsRef<'tcx>, - ) -> bool { + // We should only ever reach this case if ty_is_known_nonnull is extended + // to other types. + ref unhandled => { + debug!( + "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}", + unhandled, ty + ); + return None; + } + }) +} + +/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it +/// can, return the the type that `ty` can be safely converted to, otherwise return `None`. +/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`, +/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. +/// FIXME: This duplicates code in codegen. +crate fn repr_nullable_ptr<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + ckind: CItemKind, +) -> Option> { + debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty); + if let ty::Adt(ty_def, substs) = ty.kind { if ty_def.variants.len() != 2 { - return false; + return None; } let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields; @@ -577,30 +624,42 @@ fn is_repr_nullable_ptr( } else if variant_fields[1].is_empty() { &variant_fields[0] } else { - return false; + return None; }; if fields.len() != 1 { - return false; + return None; } - let field_ty = fields[0].ty(self.cx.tcx, substs); - if !self.ty_is_known_nonnull(field_ty) { - return false; + let field_ty = fields[0].ty(cx.tcx, substs); + if !ty_is_known_nonnull(cx, field_ty, ckind) { + return None; } - // At this point, the field's type is known to be nonnull and the parent enum is - // Option-like. If the computed size for the field and the enum are different, the non-null - // optimization isn't being applied (and we've got a problem somewhere). - let compute_size_skeleton = - |t| SizeSkeleton::compute(t, self.cx.tcx, self.cx.param_env).unwrap(); + // At this point, the field's type is known to be nonnull and the parent enum is Option-like. + // If the computed size for the field and the enum are different, the nonnull optimization isn't + // being applied (and we've got a problem somewhere). + let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap(); if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { bug!("improper_ctypes: Option nonnull optimization not applied?"); } - true + // Return the nullable type this Option-like enum can be safely represented with. + let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi; + if let Abi::Scalar(field_ty_scalar) = field_ty_abi { + match (field_ty_scalar.valid_range.start(), field_ty_scalar.valid_range.end()) { + (0, _) => unreachable!("Non-null optimisation extended to a non-zero value."), + (1, _) => { + return Some(get_nullable_type(cx, field_ty).unwrap()); + } + (start, end) => unreachable!("Unhandled start and end range: ({}, {})", start, end), + }; + } } + None +} +impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Check if the type is array and emit an unsafe type lint. fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { if let ty::Array(..) = ty.kind { @@ -681,7 +740,7 @@ fn check_variant_for_ffi( fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> FfiResult<'tcx> { use FfiResult::*; - let cx = self.cx.tcx; + let tcx = self.cx.tcx; // Protect against infinite recursion, for example // `struct S(*mut S);`. @@ -692,6 +751,10 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F } match ty.kind { + ty::Adt(def, _) if def.is_box() && matches!(self.mode, CItemKind::Definition) => { + FfiSafe + } + ty::Adt(def, substs) => { if def.is_phantom_data() { return FfiPhantom(ty); @@ -742,7 +805,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F // discriminant. if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() { // Special-case types like `Option`. - if !self.is_repr_nullable_ptr(ty, def, substs) { + if repr_nullable_ptr(self.cx, ty, self.mode).is_none() { return FfiUnsafe { ty, reason: "enum has no representation hint".into(), @@ -825,7 +888,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) if { - matches!(self.mode, ImproperCTypesMode::Definitions) + matches!(self.mode, CItemKind::Definition) && ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env) } => { @@ -851,7 +914,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F }; } - let sig = cx.erase_late_bound_regions(&sig); + let sig = tcx.erase_late_bound_regions(&sig); if !sig.output().is_unit() { let r = self.check_type_for_ffi(cache, sig.output()); match r { @@ -883,9 +946,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet>, ty: Ty<'tcx>) -> F // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, // so they are currently ignored for the purposes of this lint. - ty::Param(..) | ty::Projection(..) - if matches!(self.mode, ImproperCTypesMode::Definitions) => - { + ty::Param(..) | ty::Projection(..) if matches!(self.mode, CItemKind::Definition) => { FfiSafe } @@ -910,14 +971,14 @@ fn emit_ffi_unsafe_type_lint( help: Option<&str>, ) { let lint = match self.mode { - ImproperCTypesMode::Declarations => IMPROPER_CTYPES, - ImproperCTypesMode::Definitions => IMPROPER_CTYPES_DEFINITIONS, + CItemKind::Declaration => IMPROPER_CTYPES, + CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, }; self.cx.struct_span_lint(lint, sp, |lint| { let item_description = match self.mode { - ImproperCTypesMode::Declarations => "block", - ImproperCTypesMode::Definitions => "fn", + CItemKind::Declaration => "block", + CItemKind::Definition => "fn", }; let mut diag = lint.build(&format!( "`extern` {} uses type `{}`, which is not FFI-safe", @@ -1041,8 +1102,12 @@ fn check_foreign_static(&mut self, id: hir::HirId, span: Span) { self.check_type_for_ffi_and_report_errors(span, ty, true, false); } - fn is_internal_abi(&self, abi: Abi) -> bool { - if let Abi::Rust | Abi::RustCall | Abi::RustIntrinsic | Abi::PlatformIntrinsic = abi { + fn is_internal_abi(&self, abi: SpecAbi) -> bool { + if let SpecAbi::Rust + | SpecAbi::RustCall + | SpecAbi::RustIntrinsic + | SpecAbi::PlatformIntrinsic = abi + { true } else { false @@ -1052,7 +1117,7 @@ fn is_internal_abi(&self, abi: Abi) -> bool { impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) { - let mut vis = ImproperCTypesVisitor { cx, mode: ImproperCTypesMode::Declarations }; + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; let abi = cx.tcx.hir().get_foreign_abi(it.hir_id); if !vis.is_internal_abi(abi) { @@ -1087,7 +1152,7 @@ fn check_fn( _ => return, }; - let mut vis = ImproperCTypesVisitor { cx, mode: ImproperCTypesMode::Definitions }; + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; if !vis.is_internal_abi(abi) { vis.check_foreign_fn(hir_id, decl); }