]> git.lizzy.rs Git - rust.git/blobdiff - src/librustc_lint/types.rs
Handle structs with zst members.
[rust.git] / src / librustc_lint / types.rs
index 1affc9457b89e167492782df5bd0b1d8314b96f6..de750010ed1e6e1af3a4557d615508191e424809 100644 (file)
@@ -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;
@@ -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,54 +527,94 @@ enum FfiResult<'tcx> {
     FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> },
 }
 
-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, _)
-                if def.is_box() && matches!(self.mode, ImproperCTypesMode::Definitions) =>
-            {
-                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<Ty<'tcx>> {
+    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<T> 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<fn(..) -> _> to represent it.
+            ty
+        }
 
-    /// Check if this enum can be safely exported based on the "nullable pointer optimization".
-    /// Currently restricted to function pointers, boxes, 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<Ty<'tcx>> {
+    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;
@@ -582,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 {
@@ -686,7 +740,7 @@ fn check_variant_for_ffi(
     fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, 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);`.
@@ -697,9 +751,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> F
         }
 
         match ty.kind {
-            ty::Adt(def, _)
-                if def.is_box() && matches!(self.mode, ImproperCTypesMode::Definitions) =>
-            {
+            ty::Adt(def, _) if def.is_box() && matches!(self.mode, CItemKind::Definition) => {
                 FfiSafe
             }
 
@@ -753,7 +805,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> F
                         // discriminant.
                         if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() {
                             // Special-case types like `Option<extern fn()>`.
-                            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(),
@@ -836,7 +888,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, 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)
                 } =>
             {
@@ -862,7 +914,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, 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 {
@@ -894,9 +946,7 @@ fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, 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
             }
 
@@ -921,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",
@@ -1052,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
@@ -1063,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) {
@@ -1098,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);
         }