Skip to content

Commit 0e61890

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 a795813 commit 0e61890

File tree

3 files changed

+135
-23
lines changed

3 files changed

+135
-23
lines changed

src/librustc_lint/builtin.rs

+60-9
Original file line numberDiff line numberDiff line change
@@ -2141,14 +2141,16 @@ impl ClashingExternDeclarations {
21412141
let a_kind = &a.kind;
21422142
let b_kind = &b.kind;
21432143

2144+
use rustc_target::abi::LayoutOf;
2145+
let compare_layouts = |a, b| {
2146+
let a_layout = &cx.layout_of(a).unwrap().layout.abi;
2147+
let b_layout = &cx.layout_of(b).unwrap().layout.abi;
2148+
let result = a_layout == b_layout;
2149+
result
2150+
};
2151+
21442152
match (a_kind, b_kind) {
2145-
(Adt(..), Adt(..)) => {
2146-
// Adts are pretty straightforward: just compare the layouts.
2147-
use rustc_target::abi::LayoutOf;
2148-
let a_layout = cx.layout_of(a).unwrap().layout;
2149-
let b_layout = cx.layout_of(b).unwrap().layout;
2150-
a_layout == b_layout
2151-
}
2153+
(Adt(..), Adt(..)) => compare_layouts(a, b),
21522154
(Array(a_ty, a_const), Array(b_ty, b_const)) => {
21532155
// For arrays, we also check the constness of the type.
21542156
a_const.val == b_const.val
@@ -2165,10 +2167,10 @@ impl ClashingExternDeclarations {
21652167
a_mut == b_mut && Self::structurally_same_type(cx, a_ty, b_ty)
21662168
}
21672169
(FnDef(..), FnDef(..)) => {
2168-
// As we don't compare regions, skip_binder is fine.
21692170
let a_poly_sig = a.fn_sig(tcx);
21702171
let b_poly_sig = b.fn_sig(tcx);
21712172

2173+
// As we don't compare regions, skip_binder is fine.
21722174
let a_sig = a_poly_sig.skip_binder();
21732175
let b_sig = b_poly_sig.skip_binder();
21742176

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

src/librustc_lint/types.rs

+14-13
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> {
@@ -559,15 +559,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
559559

560560
/// Check if this enum can be safely exported based on the "nullable pointer optimization".
561561
/// Currently restricted to function pointers, references, `core::num::NonZero*`,
562-
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
563-
fn is_repr_nullable_ptr(
562+
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. If it is, return the known
563+
/// non-null field type, else None.
564+
crate fn is_repr_nullable_ptr(
564565
&self,
565566
ty: Ty<'tcx>,
566567
ty_def: &'tcx ty::AdtDef,
567568
substs: SubstsRef<'tcx>,
568-
) -> bool {
569+
) -> Option<Ty<'tcx>> {
569570
if ty_def.variants.len() != 2 {
570-
return false;
571+
return None;
571572
}
572573

573574
let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields;
@@ -577,16 +578,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
577578
} else if variant_fields[1].is_empty() {
578579
&variant_fields[0]
579580
} else {
580-
return false;
581+
return None;
581582
};
582583

583584
if fields.len() != 1 {
584-
return false;
585+
return None;
585586
}
586587

587588
let field_ty = fields[0].ty(self.cx.tcx, substs);
588589
if !self.ty_is_known_nonnull(field_ty) {
589-
return false;
590+
return None;
590591
}
591592

592593
// At this point, the field's type is known to be nonnull and the parent enum is
@@ -598,7 +599,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
598599
bug!("improper_ctypes: Option nonnull optimization not applied?");
599600
}
600601

601-
true
602+
Some(field_ty)
602603
}
603604

604605
/// Check if the type is array and emit an unsafe type lint.
@@ -742,7 +743,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
742743
// discriminant.
743744
if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() {
744745
// Special-case types like `Option<extern fn()>`.
745-
if !self.is_repr_nullable_ptr(ty, def, substs) {
746+
if self.is_repr_nullable_ptr(ty, def, substs).is_none() {
746747
return FfiUnsafe {
747748
ty,
748749
reason: "enum has no representation hint".into(),

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

+61-1
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)