about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorDavid Wood <david.wood@huawei.com>2023-07-07 18:27:16 +0100
committerDavid Wood <david.wood@huawei.com>2023-07-19 09:44:51 +0100
commitf53cef31f5ea41c9a5ab8e5335637a6873b9f0b5 (patch)
tree6100825ddfd2259c3c1a5f6dc51bafd9919548aa /compiler
parent0f16bd341eaf3ad47134c6d855a764e0099fbc93 (diff)
downloadrust-f53cef31f5ea41c9a5ab8e5335637a6873b9f0b5.tar.gz
rust-f53cef31f5ea41c9a5ab8e5335637a6873b9f0b5.zip
lint/ctypes: stricter `()` return type checks
`()` is normally FFI-unsafe, but is FFI-safe when used as a return type.
It is also desirable that a transparent newtype for `()` is FFI-safe when
used as a return type.

In order to support this, when an type was deemed FFI-unsafe, because of
a `()` type, and was used in return type - then the type was considered
FFI-safe. However, this was the wrong approach - it didn't check that the
`()` was part of a transparent newtype! The consequence of this is that
the presence of a `()` type in a more complex return type would make it
the entire type be considered safe (as long as the `()` type was the
first that the lint found) - which is obviously incorrect.

Instead, this logic is removed, and a unit return type or a transparent
wrapper around a unit is checked for directly for functions and fn-ptrs.

Signed-off-by: David Wood <david@davidtw.co>
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_lint/src/types.rs55
1 files changed, 35 insertions, 20 deletions
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index cc8a3408004..bb847578e90 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -943,6 +943,30 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
 }
 
 impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
+    // Returns `true` if `ty` is a `()`, or a `repr(transparent)` type whose only non-ZST field
+    // is a generic substituted for `()` - in either case, the type is FFI-safe when used as a
+    // return type.
+    pub fn is_unit_or_equivalent(&self, ty: Ty<'tcx>) -> bool {
+        if ty.is_unit() {
+            return true;
+        }
+
+        if let ty::Adt(def, substs) = ty.kind() && def.repr().transparent() {
+            return def.variants()
+                .iter()
+                .filter_map(|variant| transparent_newtype_field(self.cx.tcx, variant))
+                .all(|field| {
+                    let field_ty = field.ty(self.cx.tcx, substs);
+                    !field_ty.has_opaque_types() && {
+                        let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty);
+                        self.is_unit_or_equivalent(field_ty)
+                    }
+                });
+        }
+
+        false
+    }
+
     /// 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() {
@@ -1220,25 +1244,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                 }
 
                 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 {
-                        FfiSafe => {}
-                        _ => {
-                            return r;
-                        }
-                    }
-                }
                 for arg in sig.inputs() {
-                    let r = self.check_type_for_ffi(cache, *arg);
-                    match r {
+                    match self.check_type_for_ffi(cache, *arg) {
                         FfiSafe => {}
-                        _ => {
-                            return r;
-                        }
+                        r => return r,
                     }
                 }
-                FfiSafe
+
+                let ret_ty = sig.output();
+                if self.is_unit_or_equivalent(ret_ty) {
+                    return FfiSafe;
+                }
+
+                self.check_type_for_ffi(cache, ret_ty)
             }
 
             ty::Foreign(..) => FfiSafe,
@@ -1357,9 +1375,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
         }
 
         // Don't report FFI errors for unit return types. This check exists here, and not in
-        // `check_foreign_fn` (where it would make more sense) so that normalization has definitely
+        // the caller (where it would make more sense) so that normalization has definitely
         // happened.
-        if is_return_type && ty.is_unit() {
+        if is_return_type && self.is_unit_or_equivalent(ty) {
             return;
         }
 
@@ -1373,9 +1391,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
                     None,
                 );
             }
-            // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic
-            // argument, which after substitution, is `()`, then this branch can be hit.
-            FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {}
             FfiResult::FfiUnsafe { ty, reason, help } => {
                 self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
             }