Skip to content

Commit bc4819d

Browse files
committed
Address code review comments.
- Make `is_repr_nullable_ptr` freestanding again to avoid usage of ImproperCTypesVisitor in ClashingExternDeclarations (and don't accidentally revert the ParamEnv::reveal_all() fix from a week earlier) - Revise match condition for 1 Adt, 1 primitive - Generalise check for non-null type so that it would also work for ranges which exclude any single value (all bits set, for example) - Make is_repr_nullable_ptr return the representable type instead of just a boolean, to avoid adding an additional, independent "source of truth" about the FFI-compatibility of Option-like enums. Also, rename to `repr_nullable_ptr`.
1 parent 39feaa6 commit bc4819d

File tree

5 files changed

+158
-117
lines changed

5 files changed

+158
-117
lines changed

src/librustc_lint/builtin.rs

+22-52
Original file line numberDiff line numberDiff line change
@@ -2142,13 +2142,14 @@ impl ClashingExternDeclarations {
21422142
let b_kind = &b.kind;
21432143

21442144
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
2145+
let compare_layouts = |a, b| -> bool {
2146+
&cx.layout_of(a).unwrap().layout.abi == &cx.layout_of(b).unwrap().layout.abi
21502147
};
21512148

2149+
#[allow(rustc::usage_of_ty_tykind)]
2150+
let is_primitive_or_pointer =
2151+
|kind: &ty::TyKind<'_>| kind.is_primitive() || matches!(kind, RawPtr(..));
2152+
21522153
match (a_kind, b_kind) {
21532154
(Adt(..), Adt(..)) => compare_layouts(a, b),
21542155
(Array(a_ty, a_const), Array(b_ty, b_const)) => {
@@ -2196,58 +2197,27 @@ impl ClashingExternDeclarations {
21962197
| (GeneratorWitness(..), GeneratorWitness(..))
21972198
| (Projection(..), Projection(..))
21982199
| (Opaque(..), Opaque(..)) => false,
2200+
21992201
// These definitely should have been caught above.
22002202
(Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
22012203

2202-
// Disjoint kinds.
2203-
(_, _) => {
2204-
// First, check if the conversion is FFI-safe. This can happen 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-
}
2204+
// An Adt and a primitive type. This can be FFI-safe is the ADT is an enum with a
2205+
// non-null field.
2206+
(Adt(..), other_kind) | (other_kind, Adt(..))
2207+
if is_primitive_or_pointer(other_kind) =>
2208+
{
2209+
let (primitive, adt) =
2210+
if is_primitive_or_pointer(&a.kind) { (a, b) } else { (b, a) };
2211+
if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt) {
2212+
ty == primitive
2213+
} else {
2214+
compare_layouts(a, b)
22462215
}
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)
22502216
}
2217+
// Otherwise, just compare the layouts. This may fail to lint for some
2218+
// incompatible types, but at the very least, will stop reads into
2219+
// uninitialised memory.
2220+
_ => compare_layouts(a, b),
22512221
}
22522222
}
22532223
}

src/librustc_lint/types.rs

+102-55
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ use rustc_index::vec::Idx;
1111
use rustc_middle::mir::interpret::{sign_extend, truncate};
1212
use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton};
1313
use rustc_middle::ty::subst::SubstsRef;
14-
use rustc_middle::ty::{self, AdtKind, Ty, TypeFoldable};
14+
use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt, TypeFoldable};
1515
use rustc_span::source_map;
1616
use rustc_span::symbol::sym;
1717
use rustc_span::{Span, DUMMY_SP};
18+
use rustc_target::abi::Abi;
1819
use rustc_target::abi::{Integer, LayoutOf, TagEncoding, VariantIdx, Variants};
19-
use rustc_target::spec::abi::Abi;
20+
use rustc_target::spec::abi::Abi as SpecAbi;
2021

2122
use log::debug;
2223
use std::cmp;
@@ -509,14 +510,14 @@ declare_lint! {
509510

510511
declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]);
511512

512-
crate enum ImproperCTypesMode {
513+
enum ImproperCTypesMode {
513514
Declarations,
514515
Definitions,
515516
}
516517

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

522523
enum FfiResult<'tcx> {
@@ -525,48 +526,78 @@ enum FfiResult<'tcx> {
525526
FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> },
526527
}
527528

528-
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
529-
/// Is type known to be non-null?
530-
fn ty_is_known_nonnull(&self, ty: Ty<'tcx>) -> bool {
531-
match ty.kind {
532-
ty::FnPtr(_) => true,
533-
ty::Ref(..) => true,
534-
ty::Adt(field_def, substs) if field_def.repr.transparent() && !field_def.is_union() => {
535-
for field in field_def.all_fields() {
536-
let field_ty = self.cx.tcx.normalize_erasing_regions(
537-
self.cx.param_env,
538-
field.ty(self.cx.tcx, substs),
539-
);
540-
if field_ty.is_zst(self.cx.tcx, field.did) {
541-
continue;
542-
}
543-
544-
let attrs = self.cx.tcx.get_attrs(field_def.did);
545-
if attrs
546-
.iter()
547-
.any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed))
548-
|| self.ty_is_known_nonnull(field_ty)
549-
{
550-
return true;
551-
}
529+
fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
530+
let tcx = cx.tcx;
531+
match ty.kind {
532+
ty::FnPtr(_) => true,
533+
ty::Ref(..) => true,
534+
ty::Adt(field_def, substs) if field_def.repr.transparent() && !field_def.is_union() => {
535+
for field in field_def.all_fields() {
536+
let field_ty = tcx.normalize_erasing_regions(cx.param_env, field.ty(tcx, substs));
537+
if field_ty.is_zst(tcx, field.did) {
538+
continue;
552539
}
553540

554-
false
541+
let attrs = tcx.get_attrs(field_def.did);
542+
if attrs.iter().any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed))
543+
|| ty_is_known_nonnull(cx, field_ty)
544+
{
545+
return true;
546+
}
555547
}
556-
_ => false,
548+
false
557549
}
550+
_ => false,
558551
}
552+
}
553+
/// Given a potentially non-null type `ty`, return its default, nullable type.
554+
fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
555+
match ty.kind {
556+
ty::Adt(field_def, field_substs) => {
557+
let field_variants = &field_def.variants;
558+
// We hit this case for #[repr(transparent)] structs with a single
559+
// field.
560+
debug_assert!(
561+
field_variants.len() == 1 && field_variants[VariantIdx::new(0)].fields.len() == 1,
562+
"inner ty not a newtype struct"
563+
);
564+
debug_assert!(field_def.repr.transparent(), "inner ty not transparent");
565+
// As it's easy to get this wrong, it's worth noting that
566+
// `inner_field_ty` is not the same as `field_ty`: Given Option<S>,
567+
// where S is a transparent newtype of some type T, `field_ty`
568+
// gives us S, while `inner_field_ty` is T.
569+
let inner_field_ty =
570+
field_def.variants[VariantIdx::new(0)].fields[0].ty(tcx, field_substs);
571+
get_nullable_type(tcx, inner_field_ty)
572+
}
573+
ty::Int(ty) => tcx.mk_mach_int(ty),
574+
ty::Uint(ty) => tcx.mk_mach_uint(ty),
575+
ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut),
576+
// As these types are always non-null, the nullable equivalent of
577+
// Option<T> of these types are their raw pointer counterparts.
578+
ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }),
579+
ty::FnPtr(..) => {
580+
// There is no nullable equivalent for Rust's function pointers -- you
581+
// must use an Option<fn(..) -> _> to represent it.
582+
ty
583+
}
559584

560-
/// Check if this enum can be safely exported based on the "nullable pointer optimization".
561-
/// Currently restricted to function pointers, references, `core::num::NonZero*`,
562-
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. If it can, return the known
563-
/// non-null field type, otherwise return `None`.
564-
crate fn is_repr_nullable_ptr(
565-
&self,
566-
ty: Ty<'tcx>,
567-
ty_def: &'tcx ty::AdtDef,
568-
substs: SubstsRef<'tcx>,
569-
) -> Option<Ty<'tcx>> {
585+
// We should only ever reach this case if ty_is_known_nonnull is extended
586+
// to other types.
587+
ref unhandled => {
588+
unreachable!("Unhandled scalar kind: {:?} while checking {:?}", unhandled, ty)
589+
}
590+
}
591+
}
592+
593+
/// Check if this `ty` can be safely exported based on the "nullable pointer optimization".
594+
/// Currently restricted to function pointers, references, `core::num::NonZero*`,
595+
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. If it can, return the nullable type
596+
/// that `ty` can be converted to, else None.
597+
/// FIXME: This duplicates code in codegen.
598+
crate fn repr_nullable_ptr<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
599+
debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty);
600+
if let ty::Adt(ty_def, substs) = ty.kind {
570601
if ty_def.variants.len() != 2 {
571602
return None;
572603
}
@@ -585,23 +616,35 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
585616
return None;
586617
}
587618

588-
let field_ty = fields[0].ty(self.cx.tcx, substs);
589-
if !self.ty_is_known_nonnull(field_ty) {
619+
let field_ty = fields[0].ty(cx.tcx, substs);
620+
if !ty_is_known_nonnull(cx, field_ty) {
590621
return None;
591622
}
592623

593-
// At this point, the field's type is known to be nonnull and the parent enum is
594-
// Option-like. If the computed size for the field and the enum are different, the non-null
595-
// optimization isn't being applied (and we've got a problem somewhere).
596-
let compute_size_skeleton =
597-
|t| SizeSkeleton::compute(t, self.cx.tcx, self.cx.param_env).unwrap();
624+
// At this point, the field's type is known to be nonnull and the parent enum is Option-like.
625+
// If the computed size for the field and the enum are different, the nonnull optimization isn't
626+
// being applied (and we've got a problem somewhere).
627+
let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap();
598628
if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) {
599629
bug!("improper_ctypes: Option nonnull optimization not applied?");
600630
}
601631

602-
Some(field_ty)
632+
// Return the nullable type this Option-like enum can be safely represented with.
633+
let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi;
634+
if let Abi::Scalar(field_ty_scalar) = field_ty_abi {
635+
match (field_ty_scalar.valid_range.start(), field_ty_scalar.valid_range.end()) {
636+
(0, _) => bug!("Non-null optimisation extended to a non-zero value."),
637+
(1, _) => {
638+
return Some(get_nullable_type(cx.tcx, field_ty));
639+
}
640+
(start, end) => unreachable!("Unhandled start and end range: ({}, {})", start, end),
641+
};
642+
}
603643
}
644+
None
645+
}
604646

647+
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
605648
/// Check if the type is array and emit an unsafe type lint.
606649
fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
607650
if let ty::Array(..) = ty.kind {
@@ -682,7 +725,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
682725
fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
683726
use FfiResult::*;
684727

685-
let cx = self.cx.tcx;
728+
let tcx = self.cx.tcx;
686729

687730
// Protect against infinite recursion, for example
688731
// `struct S(*mut S);`.
@@ -743,7 +786,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
743786
// discriminant.
744787
if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() {
745788
// Special-case types like `Option<extern fn()>`.
746-
if self.is_repr_nullable_ptr(ty, def, substs).is_none() {
789+
if repr_nullable_ptr(self.cx, ty).is_none() {
747790
return FfiUnsafe {
748791
ty,
749792
reason: "enum has no representation hint".into(),
@@ -852,7 +895,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
852895
};
853896
}
854897

855-
let sig = cx.erase_late_bound_regions(&sig);
898+
let sig = tcx.erase_late_bound_regions(&sig);
856899
if !sig.output().is_unit() {
857900
let r = self.check_type_for_ffi(cache, sig.output());
858901
match r {
@@ -1042,8 +1085,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10421085
self.check_type_for_ffi_and_report_errors(span, ty, true, false);
10431086
}
10441087

1045-
fn is_internal_abi(&self, abi: Abi) -> bool {
1046-
if let Abi::Rust | Abi::RustCall | Abi::RustIntrinsic | Abi::PlatformIntrinsic = abi {
1088+
fn is_internal_abi(&self, abi: SpecAbi) -> bool {
1089+
if let SpecAbi::Rust
1090+
| SpecAbi::RustCall
1091+
| SpecAbi::RustIntrinsic
1092+
| SpecAbi::PlatformIntrinsic = abi
1093+
{
10471094
true
10481095
} else {
10491096
false

src/librustc_middle/ty/sty.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ pub enum TyKind<'tcx> {
206206
Error(DelaySpanBugEmitted),
207207
}
208208

209+
impl TyKind<'tcx> {
210+
#[inline]
211+
pub fn is_primitive(&self) -> bool {
212+
match self {
213+
Bool | Char | Int(_) | Uint(_) | Float(_) => true,
214+
_ => false,
215+
}
216+
}
217+
}
218+
209219
/// A type that is not publicly constructable. This prevents people from making `TyKind::Error`
210220
/// except through `tcx.err*()`.
211221
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
@@ -1710,10 +1720,7 @@ impl<'tcx> TyS<'tcx> {
17101720

17111721
#[inline]
17121722
pub fn is_primitive(&self) -> bool {
1713-
match self.kind {
1714-
Bool | Char | Int(_) | Uint(_) | Float(_) => true,
1715-
_ => false,
1716-
}
1723+
self.kind.is_primitive()
17171724
}
17181725

17191726
#[inline]

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

+5
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ mod transparent {
181181
use super::T;
182182
extern "C" {
183183
fn transparent() -> T;
184+
fn transparent_incorrect() -> T;
184185
}
185186
}
186187

@@ -189,6 +190,10 @@ mod transparent {
189190
// Shouldn't warn here, because repr(transparent) guarantees that T's layout is the
190191
// same as just the usize.
191192
fn transparent() -> usize;
193+
194+
// Should warn, because there's a signedness conversion here:
195+
fn transparent_incorrect() -> isize;
196+
//~^ WARN `transparent_incorrect` redeclared with a different signature
192197
}
193198
}
194199
}

0 commit comments

Comments
 (0)