Skip to content

Commit 3eaead7

Browse files
committed
Don't emit clashing decl lint for FFI-safe enums.
An example of an FFI-safe enum conversion is when converting Option<NonZeroUsize> to usize. Because the Some value must be non-zero, rustc can use 0 to represent the None variant, making this conversion is safe. Furthermore, it can be relied on (and removing this optimisation already would be a breaking change).
1 parent 4da72f5 commit 3eaead7

File tree

3 files changed

+137
-25
lines changed

3 files changed

+137
-25
lines changed

src/librustc_lint/builtin.rs

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,14 +2155,16 @@ impl ClashingExternDeclarations {
21552155
let a_kind = &a.kind;
21562156
let b_kind = &b.kind;
21572157

2158+
use rustc_target::abi::LayoutOf;
2159+
let compare_layouts = |a, b| {
2160+
let a_layout = &cx.layout_of(a).unwrap().layout.abi;
2161+
let b_layout = &cx.layout_of(b).unwrap().layout.abi;
2162+
let result = a_layout == b_layout;
2163+
result
2164+
};
2165+
21582166
match (a_kind, b_kind) {
2159-
(Adt(..), Adt(..)) => {
2160-
// Adts are pretty straightforward: just compare the layouts.
2161-
use rustc_target::abi::LayoutOf;
2162-
let a_layout = cx.layout_of(a).unwrap().layout;
2163-
let b_layout = cx.layout_of(b).unwrap().layout;
2164-
a_layout == b_layout
2165-
}
2167+
(Adt(..), Adt(..)) => compare_layouts(a, b),
21662168
(Array(a_ty, a_const), Array(b_ty, b_const)) => {
21672169
// For arrays, we also check the constness of the type.
21682170
a_const.val == b_const.val
@@ -2179,10 +2181,10 @@ impl ClashingExternDeclarations {
21792181
a_mut == b_mut && Self::structurally_same_type(cx, a_ty, b_ty)
21802182
}
21812183
(FnDef(..), FnDef(..)) => {
2182-
// As we don't compare regions, skip_binder is fine.
21832184
let a_poly_sig = a.fn_sig(tcx);
21842185
let b_poly_sig = b.fn_sig(tcx);
21852186

2187+
// As we don't compare regions, skip_binder is fine.
21862188
let a_sig = a_poly_sig.skip_binder();
21872189
let b_sig = b_poly_sig.skip_binder();
21882190

@@ -2210,7 +2212,56 @@ impl ClashingExternDeclarations {
22102212
| (Opaque(..), Opaque(..)) => false,
22112213
// These definitely should have been caught above.
22122214
(Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
2213-
_ => false,
2215+
2216+
// Disjoint kinds.
2217+
(_, _) => {
2218+
// First, check if the conversion is FFI-safe. This can be so if the type is an
2219+
// enum with a non-null field (see improper_ctypes).
2220+
let is_primitive_or_pointer =
2221+
|ty: Ty<'tcx>| ty.is_primitive() || matches!(ty.kind, RawPtr(..));
2222+
if (is_primitive_or_pointer(a) || is_primitive_or_pointer(b))
2223+
&& !(is_primitive_or_pointer(a) && is_primitive_or_pointer(b))
2224+
&& (matches!(a_kind, Adt(..)) || matches!(b_kind, Adt(..)))
2225+
/* ie, 1 adt and 1 primitive */
2226+
{
2227+
let (primitive_ty, adt_ty) =
2228+
if is_primitive_or_pointer(a) { (a, b) } else { (b, a) };
2229+
// First, check that the Adt is FFI-safe to use.
2230+
use crate::types::{ImproperCTypesMode, ImproperCTypesVisitor};
2231+
let vis =
2232+
ImproperCTypesVisitor { cx, mode: ImproperCTypesMode::Declarations };
2233+
2234+
if let Adt(def, substs) = adt_ty.kind {
2235+
let repr_nullable = vis.is_repr_nullable_ptr(adt_ty, def, substs);
2236+
if let Some(safe_ty) = repr_nullable {
2237+
let safe_ty_layout = &cx.layout_of(safe_ty).unwrap();
2238+
let primitive_ty_layout = &cx.layout_of(primitive_ty).unwrap();
2239+
2240+
use rustc_target::abi::Abi::*;
2241+
match (&safe_ty_layout.abi, &primitive_ty_layout.abi) {
2242+
(Scalar(safe), Scalar(primitive)) => {
2243+
// The two types are safe to convert between if `safe` is
2244+
// the non-zero version of `primitive`.
2245+
use std::ops::RangeInclusive;
2246+
2247+
let safe_range: &RangeInclusive<_> = &safe.valid_range;
2248+
let primitive_range: &RangeInclusive<_> =
2249+
&primitive.valid_range;
2250+
2251+
return primitive_range.end() == safe_range.end()
2252+
// This check works for both signed and unsigned types due to wraparound.
2253+
&& *safe_range.start() == 1
2254+
&& *primitive_range.start() == 0;
2255+
}
2256+
_ => {}
2257+
}
2258+
}
2259+
}
2260+
}
2261+
// Otherwise, just compare the layouts. This may be underapproximate, but at
2262+
// the very least, will stop reads into uninitialised memory.
2263+
compare_layouts(a, b)
2264+
}
22142265
}
22152266
}
22162267
}

src/librustc_lint/types.rs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -509,14 +509,14 @@ declare_lint! {
509509

510510
declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]);
511511

512-
enum ImproperCTypesMode {
512+
crate enum ImproperCTypesMode {
513513
Declarations,
514514
Definitions,
515515
}
516516

517-
struct ImproperCTypesVisitor<'a, 'tcx> {
518-
cx: &'a LateContext<'tcx>,
519-
mode: ImproperCTypesMode,
517+
crate struct ImproperCTypesVisitor<'a, 'tcx> {
518+
crate cx: &'a LateContext<'tcx>,
519+
crate mode: ImproperCTypesMode,
520520
}
521521

522522
enum FfiResult<'tcx> {
@@ -562,17 +562,18 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
562562
}
563563
}
564564

565-
/// Check if this enum can be safely exported based on the "nullable pointer optimization".
566-
/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`,
567-
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
568-
fn is_repr_nullable_ptr(
565+
/// Check if this enum can be safely exported based on the "nullable pointer optimization". If
566+
/// the type is it is, return the known non-null field type, else None. Currently restricted
567+
/// to function pointers, boxes, references, `core::num::NonZero*`, `core::ptr::NonNull`, and
568+
/// `#[repr(transparent)]` newtypes.
569+
crate fn is_repr_nullable_ptr(
569570
&self,
570571
ty: Ty<'tcx>,
571572
ty_def: &'tcx ty::AdtDef,
572573
substs: SubstsRef<'tcx>,
573-
) -> bool {
574+
) -> Option<Ty<'tcx>> {
574575
if ty_def.variants.len() != 2 {
575-
return false;
576+
return None;
576577
}
577578

578579
let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields;
@@ -582,16 +583,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
582583
} else if variant_fields[1].is_empty() {
583584
&variant_fields[0]
584585
} else {
585-
return false;
586+
return None;
586587
};
587588

588589
if fields.len() != 1 {
589-
return false;
590+
return None;
590591
}
591592

592593
let field_ty = fields[0].ty(self.cx.tcx, substs);
593594
if !self.ty_is_known_nonnull(field_ty) {
594-
return false;
595+
return None;
595596
}
596597

597598
// At this point, the field's type is known to be nonnull and the parent enum is
@@ -603,7 +604,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
603604
bug!("improper_ctypes: Option nonnull optimization not applied?");
604605
}
605606

606-
true
607+
Some(field_ty)
607608
}
608609

609610
/// Check if the type is array and emit an unsafe type lint.
@@ -753,7 +754,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
753754
// discriminant.
754755
if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() {
755756
// Special-case types like `Option<extern fn()>`.
756-
if !self.is_repr_nullable_ptr(ty, def, substs) {
757+
if self.is_repr_nullable_ptr(ty, def, substs).is_none() {
757758
return FfiUnsafe {
758759
ty,
759760
reason: "enum has no representation hint".into(),

src/test/ui/lint/clashing-extern-fn.stderr

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,65 @@ LL | fn draw_point(p: Point);
105105
= note: expected `unsafe extern "C" fn(sameish_members::a::Point)`
106106
found `unsafe extern "C" fn(sameish_members::b::Point)`
107107

108-
warning: 8 warnings emitted
108+
warning: `missing_return_type` redeclared with a different signature
109+
--> $DIR/clashing-extern-fn.rs:208:13
110+
|
111+
LL | fn missing_return_type() -> usize;
112+
| ---------------------------------- `missing_return_type` previously declared here
113+
...
114+
LL | fn missing_return_type();
115+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
116+
|
117+
= note: expected `unsafe extern "C" fn() -> usize`
118+
found `unsafe extern "C" fn()`
119+
120+
warning: `non_zero_usize` redeclared with a different signature
121+
--> $DIR/clashing-extern-fn.rs:226:13
122+
|
123+
LL | fn non_zero_usize() -> core::num::NonZeroUsize;
124+
| ----------------------------------------------- `non_zero_usize` previously declared here
125+
...
126+
LL | fn non_zero_usize() -> usize;
127+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
128+
|
129+
= note: expected `unsafe extern "C" fn() -> std::num::NonZeroUsize`
130+
found `unsafe extern "C" fn() -> usize`
131+
132+
warning: `non_null_ptr` redeclared with a different signature
133+
--> $DIR/clashing-extern-fn.rs:228:13
134+
|
135+
LL | fn non_null_ptr() -> core::ptr::NonNull<usize>;
136+
| ----------------------------------------------- `non_null_ptr` previously declared here
137+
...
138+
LL | fn non_null_ptr() -> *const usize;
139+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
140+
|
141+
= note: expected `unsafe extern "C" fn() -> std::ptr::NonNull<usize>`
142+
found `unsafe extern "C" fn() -> *const usize`
143+
144+
warning: `option_non_zero_usize_incorrect` redeclared with a different signature
145+
--> $DIR/clashing-extern-fn.rs:254:13
146+
|
147+
LL | fn option_non_zero_usize_incorrect() -> usize;
148+
| ---------------------------------------------- `option_non_zero_usize_incorrect` previously declared here
149+
...
150+
LL | fn option_non_zero_usize_incorrect() -> isize;
151+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
152+
|
153+
= note: expected `unsafe extern "C" fn() -> usize`
154+
found `unsafe extern "C" fn() -> isize`
155+
156+
warning: `option_non_null_ptr_incorrect` redeclared with a different signature
157+
--> $DIR/clashing-extern-fn.rs:256:13
158+
|
159+
LL | fn option_non_null_ptr_incorrect() -> *const usize;
160+
| --------------------------------------------------- `option_non_null_ptr_incorrect` previously declared here
161+
...
162+
LL | fn option_non_null_ptr_incorrect() -> *const isize;
163+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this signature doesn't match the previous declaration
164+
|
165+
= note: expected `unsafe extern "C" fn() -> *const usize`
166+
found `unsafe extern "C" fn() -> *const isize`
167+
168+
warning: 13 warnings emitted
109169

0 commit comments

Comments
 (0)