Skip to content

Commit a31dc8e

Browse files
committed
Allow null-pointer-optimized enums in FFI if their underlying representation is FFI safe
This allows types like Option<NonZeroU8> to be used in FFI without triggering the improper_ctypes lint. This works by changing the is_repr_nullable_ptr function to consider an enum E to be FFI-safe if: - E has no explicit #[repr(...)]. - It only has two variants. - One of those variants is empty (meaning it has no fields). - The other variant has only one field. - That field is one of the following: - &T - &mut T - extern "C" fn - core::num::NonZero* - core::ptr::NonNull<T> - #[repr(transparent)] struct wrapper around one of the types in this list. - The size of E and its field are both known and are both the same size (implying E is participating in the nonnull optimization).
1 parent 37ff5d3 commit a31dc8e

File tree

9 files changed

+156
-49
lines changed

9 files changed

+156
-49
lines changed

src/libcore/num/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ assert_eq!(size_of::<Option<core::num::", stringify!($Ty), ">>(), size_of::<", s
5050
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
5151
#[repr(transparent)]
5252
#[rustc_layout_scalar_valid_range_start(1)]
53+
#[cfg_attr(not(stage0), rustc_nonnull_optimization_guaranteed)]
5354
pub struct $Ty($Int);
5455
}
5556

src/libcore/ptr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2938,6 +2938,7 @@ impl<'a, T: ?Sized> From<NonNull<T>> for Unique<T> {
29382938
#[stable(feature = "nonnull", since = "1.25.0")]
29392939
#[repr(transparent)]
29402940
#[rustc_layout_scalar_valid_range_start(1)]
2941+
#[cfg_attr(not(stage0), rustc_nonnull_optimization_guaranteed)]
29412942
pub struct NonNull<T: ?Sized> {
29422943
pointer: *const T,
29432944
}

src/librustc_lint/types.rs

+70-38
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
#![allow(non_snake_case)]
22

33
use rustc::hir::{ExprKind, Node};
4+
use crate::hir::def_id::DefId;
45
use rustc::hir::lowering::is_range_literal;
56
use rustc::ty::subst::SubstsRef;
67
use rustc::ty::{self, AdtKind, ParamEnv, Ty, TyCtxt};
7-
use rustc::ty::layout::{self, IntegerExt, LayoutOf, VariantIdx};
8+
use rustc::ty::layout::{self, IntegerExt, LayoutOf, VariantIdx, SizeSkeleton};
89
use rustc::{lint, util};
910
use rustc_data_structures::indexed_vec::Idx;
1011
use util::nodemap::FxHashSet;
@@ -14,11 +15,11 @@ use lint::{LintPass, LateLintPass};
1415
use std::cmp;
1516
use std::{i8, i16, i32, i64, u8, u16, u32, u64, f32, f64};
1617

17-
use syntax::{ast, attr};
18+
use syntax::{ast, attr, source_map};
1819
use syntax::errors::Applicability;
20+
use syntax::symbol::sym;
1921
use rustc_target::spec::abi::Abi;
2022
use syntax_pos::Span;
21-
use syntax::source_map;
2223

2324
use rustc::hir;
2425

@@ -522,42 +523,79 @@ enum FfiResult<'tcx> {
522523
},
523524
}
524525

526+
fn is_zst<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, did: DefId, ty: Ty<'tcx>) -> bool {
527+
tcx.layout_of(tcx.param_env(did).and(ty)).map(|layout| layout.is_zst()).unwrap_or(false)
528+
}
529+
530+
fn ty_is_known_nonnull<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, ty: Ty<'tcx>) -> bool {
531+
match ty.sty {
532+
ty::FnPtr(_) => true,
533+
ty::Ref(..) => true,
534+
ty::Adt(field_def, substs) if field_def.repr.transparent() && field_def.is_struct() => {
535+
for field in &field_def.non_enum_variant().fields {
536+
let field_ty = tcx.normalize_erasing_regions(
537+
ParamEnv::reveal_all(),
538+
field.ty(tcx, substs),
539+
);
540+
if is_zst(tcx, field.did, field_ty) {
541+
continue;
542+
}
543+
544+
let attrs = tcx.get_attrs(field_def.did);
545+
if attrs.iter().any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed)) ||
546+
ty_is_known_nonnull(tcx, field_ty) {
547+
return true;
548+
}
549+
}
550+
551+
false
552+
}
553+
_ => false,
554+
}
555+
}
556+
525557
/// Check if this enum can be safely exported based on the
526558
/// "nullable pointer optimization". Currently restricted
527-
/// to function pointers and references, but could be
528-
/// expanded to cover NonZero raw pointers and newtypes.
559+
/// to function pointers, references, core::num::NonZero*,
560+
/// core::ptr::NonNull, and #[repr(transparent)] newtypes.
529561
/// FIXME: This duplicates code in codegen.
530562
fn is_repr_nullable_ptr<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
531-
def: &'tcx ty::AdtDef,
563+
ty: Ty<'tcx>,
564+
ty_def: &'tcx ty::AdtDef,
532565
substs: SubstsRef<'tcx>)
533566
-> bool {
534-
if def.variants.len() == 2 {
535-
let data_idx;
567+
if ty_def.variants.len() != 2 {
568+
return false;
569+
}
536570

537-
let zero = VariantIdx::new(0);
538-
let one = VariantIdx::new(1);
571+
let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields;
572+
let variant_fields = [get_variant_fields(0), get_variant_fields(1)];
573+
let fields = if variant_fields[0].is_empty() {
574+
&variant_fields[1]
575+
} else if variant_fields[1].is_empty() {
576+
&variant_fields[0]
577+
} else {
578+
return false;
579+
};
539580

540-
if def.variants[zero].fields.is_empty() {
541-
data_idx = one;
542-
} else if def.variants[one].fields.is_empty() {
543-
data_idx = zero;
544-
} else {
545-
return false;
546-
}
581+
if fields.len() != 1 {
582+
return false;
583+
}
547584

548-
if def.variants[data_idx].fields.len() == 1 {
549-
match def.variants[data_idx].fields[0].ty(tcx, substs).sty {
550-
ty::FnPtr(_) => {
551-
return true;
552-
}
553-
ty::Ref(..) => {
554-
return true;
555-
}
556-
_ => {}
557-
}
558-
}
585+
let field_ty = fields[0].ty(tcx, substs);
586+
if !ty_is_known_nonnull(tcx, field_ty) {
587+
return false;
559588
}
560-
false
589+
590+
// At this point, the field's type is known to be nonnull and the parent enum is Option-like.
591+
// If the computed size for the field and the enum are different, the nonnull optimization isn't
592+
// being applied (and we've got a problem somewhere).
593+
let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, ParamEnv::reveal_all()).unwrap();
594+
if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) {
595+
bug!("improper_ctypes: Option nonnull optimization not applied?");
596+
}
597+
598+
true
561599
}
562600

563601
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
@@ -612,14 +650,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
612650
);
613651
// repr(transparent) types are allowed to have arbitrary ZSTs, not just
614652
// PhantomData -- skip checking all ZST fields
615-
if def.repr.transparent() {
616-
let is_zst = cx
617-
.layout_of(cx.param_env(field.did).and(field_ty))
618-
.map(|layout| layout.is_zst())
619-
.unwrap_or(false);
620-
if is_zst {
621-
continue;
622-
}
653+
if def.repr.transparent() && is_zst(cx, field.did, field_ty) {
654+
continue;
623655
}
624656
let r = self.check_type_for_ffi(cache, field_ty);
625657
match r {
@@ -682,7 +714,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
682714
// discriminant.
683715
if !def.repr.c() && def.repr.int.is_none() {
684716
// Special-case types like `Option<extern fn()>`.
685-
if !is_repr_nullable_ptr(cx, def, substs) {
717+
if !is_repr_nullable_ptr(cx, ty, def, substs) {
686718
return FfiUnsafe {
687719
ty: ty,
688720
reason: "enum has no representation hint",

src/libsyntax/feature_gate.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,13 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
11341134
is just used to enable niche optimizations in libcore \
11351135
and will never be stable",
11361136
cfg_fn!(rustc_attrs))),
1137+
(sym::rustc_nonnull_optimization_guaranteed, Whitelisted, template!(Word),
1138+
Gated(Stability::Unstable,
1139+
sym::rustc_attrs,
1140+
"the `#[rustc_nonnull_optimization_guaranteed]` attribute \
1141+
is just used to enable niche optimizations in libcore \
1142+
and will never be stable",
1143+
cfg_fn!(rustc_attrs))),
11371144
(sym::rustc_regions, Normal, template!(Word), Gated(Stability::Unstable,
11381145
sym::rustc_attrs,
11391146
"the `#[rustc_regions]` attribute \

src/libsyntax_pos/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ symbols! {
491491
rustc_layout_scalar_valid_range_end,
492492
rustc_layout_scalar_valid_range_start,
493493
rustc_mir,
494+
rustc_nonnull_optimization_guaranteed,
494495
rustc_object_lifetime_default,
495496
rustc_on_unimplemented,
496497
rustc_outlives,

src/test/ui/feature-gates/feature-gate-rustc-attrs-1.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44

55
#[rustc_variance] //~ ERROR the `#[rustc_variance]` attribute is just used for rustc unit tests and will never be stable
66
#[rustc_error] //~ ERROR the `#[rustc_error]` attribute is just used for rustc unit tests and will never be stable
7+
#[rustc_nonnull_optimization_guaranteed] //~ ERROR the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to enable niche optimizations in libcore and will never be stable
78

89
fn main() {}

src/test/ui/feature-gates/feature-gate-rustc-attrs-1.stderr

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ LL | #[rustc_error]
1616
= note: for more information, see https://github.com/rust-lang/rust/issues/29642
1717
= help: add #![feature(rustc_attrs)] to the crate attributes to enable
1818

19-
error: aborting due to 2 previous errors
19+
error[E0658]: the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to enable niche optimizations in libcore and will never be stable
20+
--> $DIR/feature-gate-rustc-attrs-1.rs:7:1
21+
|
22+
LL | #[rustc_nonnull_optimization_guaranteed]
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24+
|
25+
= note: for more information, see https://github.com/rust-lang/rust/issues/29642
26+
= help: add #![feature(rustc_attrs)] to the crate attributes to enable
27+
28+
error: aborting due to 3 previous errors
2029

2130
For more information about this error, try `rustc --explain E0658`.

src/test/ui/lint/lint-ctypes-enum.rs

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![deny(improper_ctypes)]
22
#![allow(dead_code)]
33

4+
use std::num;
5+
46
enum Z { }
57
enum U { A }
68
enum B { C, D }
@@ -15,14 +17,39 @@ enum U8 { A, B, C }
1517
#[repr(isize)]
1618
enum Isize { A, B, C }
1719

20+
#[repr(transparent)]
21+
struct Transparent<T>(T, std::marker::PhantomData<Z>);
22+
23+
struct Rust<T>(T);
24+
1825
extern {
1926
fn zf(x: Z);
2027
fn uf(x: U); //~ ERROR enum has no representation hint
2128
fn bf(x: B); //~ ERROR enum has no representation hint
2229
fn tf(x: T); //~ ERROR enum has no representation hint
23-
fn reprc(x: ReprC);
24-
fn u8(x: U8);
25-
fn isize(x: Isize);
30+
fn repr_c(x: ReprC);
31+
fn repr_u8(x: U8);
32+
fn repr_isize(x: Isize);
33+
fn option_ref(x: Option<&'static u8>);
34+
fn option_fn(x: Option<extern "C" fn()>);
35+
fn nonnull(x: Option<std::ptr::NonNull<u8>>);
36+
fn nonzero_u8(x: Option<num::NonZeroU8>);
37+
fn nonzero_u16(x: Option<num::NonZeroU16>);
38+
fn nonzero_u32(x: Option<num::NonZeroU32>);
39+
fn nonzero_u64(x: Option<num::NonZeroU64>);
40+
fn nonzero_u128(x: Option<num::NonZeroU128>);
41+
//~^ ERROR 128-bit integers don't currently have a known stable ABI
42+
fn nonzero_usize(x: Option<num::NonZeroUsize>);
43+
fn nonzero_i8(x: Option<num::NonZeroI8>);
44+
fn nonzero_i16(x: Option<num::NonZeroI16>);
45+
fn nonzero_i32(x: Option<num::NonZeroI32>);
46+
fn nonzero_i64(x: Option<num::NonZeroI64>);
47+
fn nonzero_i128(x: Option<num::NonZeroI128>);
48+
//~^ ERROR 128-bit integers don't currently have a known stable ABI
49+
fn nonzero_isize(x: Option<num::NonZeroIsize>);
50+
fn repr_transparent(x: Option<Transparent<num::NonZeroU8>>);
51+
fn repr_rust(x: Option<Rust<num::NonZeroU8>>); //~ ERROR enum has no representation hint
52+
fn no_result(x: Result<(), num::NonZeroI32>); //~ ERROR enum has no representation hint
2653
}
2754

2855
pub fn main() { }
+35-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: `extern` block uses type `U` which is not FFI-safe: enum has no representation hint
2-
--> $DIR/lint-ctypes-enum.rs:20:13
2+
--> $DIR/lint-ctypes-enum.rs:27:13
33
|
44
LL | fn uf(x: U);
55
| ^
@@ -11,36 +11,64 @@ LL | #![deny(improper_ctypes)]
1111
| ^^^^^^^^^^^^^^^
1212
= help: consider adding a #[repr(...)] attribute to this enum
1313
note: type defined here
14-
--> $DIR/lint-ctypes-enum.rs:5:1
14+
--> $DIR/lint-ctypes-enum.rs:7:1
1515
|
1616
LL | enum U { A }
1717
| ^^^^^^^^^^^^
1818

1919
error: `extern` block uses type `B` which is not FFI-safe: enum has no representation hint
20-
--> $DIR/lint-ctypes-enum.rs:21:13
20+
--> $DIR/lint-ctypes-enum.rs:28:13
2121
|
2222
LL | fn bf(x: B);
2323
| ^
2424
|
2525
= help: consider adding a #[repr(...)] attribute to this enum
2626
note: type defined here
27-
--> $DIR/lint-ctypes-enum.rs:6:1
27+
--> $DIR/lint-ctypes-enum.rs:8:1
2828
|
2929
LL | enum B { C, D }
3030
| ^^^^^^^^^^^^^^^
3131

3232
error: `extern` block uses type `T` which is not FFI-safe: enum has no representation hint
33-
--> $DIR/lint-ctypes-enum.rs:22:13
33+
--> $DIR/lint-ctypes-enum.rs:29:13
3434
|
3535
LL | fn tf(x: T);
3636
| ^
3737
|
3838
= help: consider adding a #[repr(...)] attribute to this enum
3939
note: type defined here
40-
--> $DIR/lint-ctypes-enum.rs:7:1
40+
--> $DIR/lint-ctypes-enum.rs:9:1
4141
|
4242
LL | enum T { E, F, G }
4343
| ^^^^^^^^^^^^^^^^^^
4444

45-
error: aborting due to 3 previous errors
45+
error: `extern` block uses type `u128` which is not FFI-safe: 128-bit integers don't currently have a known stable ABI
46+
--> $DIR/lint-ctypes-enum.rs:40:23
47+
|
48+
LL | fn nonzero_u128(x: Option<num::NonZeroU128>);
49+
| ^^^^^^^^^^^^^^^^^^^^^^^^
50+
51+
error: `extern` block uses type `i128` which is not FFI-safe: 128-bit integers don't currently have a known stable ABI
52+
--> $DIR/lint-ctypes-enum.rs:47:23
53+
|
54+
LL | fn nonzero_i128(x: Option<num::NonZeroI128>);
55+
| ^^^^^^^^^^^^^^^^^^^^^^^^
56+
57+
error: `extern` block uses type `std::option::Option<Rust<std::num::NonZeroU8>>` which is not FFI-safe: enum has no representation hint
58+
--> $DIR/lint-ctypes-enum.rs:51:20
59+
|
60+
LL | fn repr_rust(x: Option<Rust<num::NonZeroU8>>);
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62+
|
63+
= help: consider adding a #[repr(...)] attribute to this enum
64+
65+
error: `extern` block uses type `std::result::Result<(), std::num::NonZeroI32>` which is not FFI-safe: enum has no representation hint
66+
--> $DIR/lint-ctypes-enum.rs:52:20
67+
|
68+
LL | fn no_result(x: Result<(), num::NonZeroI32>);
69+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
70+
|
71+
= help: consider adding a #[repr(...)] attribute to this enum
72+
73+
error: aborting due to 7 previous errors
4674

0 commit comments

Comments
 (0)