Skip to content

Commit fa376c8

Browse files
committed
ImproperCTypes: handle the Option<pattern> case
Properly treat the fact that a pattern can create assumptions that are used by Option-like enums to be smaller, making those enums FFI-safe without `[repr(C)]`.
1 parent d1ed3c7 commit fa376c8

File tree

3 files changed

+197
-30
lines changed

3 files changed

+197
-30
lines changed

compiler/rustc_lint/src/types.rs

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::iter;
22

3-
use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange};
3+
use rustc_abi::{BackendRepr, Size, TagEncoding, Variants, WrappingRange};
44
use rustc_hir::{Expr, ExprKind, HirId, LangItem};
55
use rustc_middle::bug;
66
use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton};
7-
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
7+
use rustc_middle::ty::{self, Const, ScalarInt, Ty, TyCtxt, TypeVisitableExt};
88
use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
99
use rustc_span::{Span, Symbol, sym};
1010
use tracing::debug;
@@ -864,7 +864,7 @@ fn is_niche_optimization_candidate<'tcx>(
864864
/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it
865865
/// can, return the type that `ty` can be safely converted to, otherwise return `None`.
866866
/// Currently restricted to function pointers, boxes, references, `core::num::NonZero`,
867-
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
867+
/// `core::ptr::NonNull`, `#[repr(transparent)]` newtypes, and int-range pattern types.
868868
pub(crate) fn repr_nullable_ptr<'tcx>(
869869
tcx: TyCtxt<'tcx>,
870870
typing_env: ty::TypingEnv<'tcx>,
@@ -893,6 +893,14 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
893893
_ => return None,
894894
};
895895

896+
if let ty::Pat(base, pat) = field_ty.kind() {
897+
if pattern_has_disallowed_values(*pat) || matches!(base.kind(), ty::Char) {
898+
return get_nullable_type_from_pat(tcx, typing_env, *base, *pat);
899+
} else {
900+
return None;
901+
}
902+
}
903+
896904
if !ty_is_known_nonnull(tcx, typing_env, field_ty) {
897905
return None;
898906
}
@@ -934,6 +942,151 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
934942
}
935943
}
936944

945+
/// Returns whether a pattern type actually has disallowed values.
946+
pub(crate) fn pattern_has_disallowed_values<'tcx>(pat: ty::Pattern<'tcx>) -> bool {
947+
// note the logic in this function assumes that signed ints use one's complement representation,
948+
// which I believe is a requirement for rust
949+
950+
/// Find numeric metadata on a pair of range bounds.
951+
/// If None, assume that there are no bounds specified
952+
/// and that this is a usize. in other words, all values are allowed.
953+
fn unwrap_start_end<'tcx>(
954+
start: Const<'tcx>,
955+
end: Const<'tcx>,
956+
) -> (bool, Size, ScalarInt, ScalarInt) {
957+
let usable_bound = match (start.try_to_value(), end.try_to_value()) {
958+
(Some(ty), _) | (_, Some(ty)) => ty,
959+
(None, None) => bug!(
960+
"pattern range should have at least one defined value: {:?} - {:?}",
961+
start,
962+
end,
963+
),
964+
};
965+
let usable_size = usable_bound.valtree.unwrap_leaf().size();
966+
let is_signed = match usable_bound.ty.kind() {
967+
ty::Int(_) => true,
968+
ty::Uint(_) | ty::Char => false,
969+
kind @ _ => bug!("unexpected non-scalar base for pattern bounds: {:?}", kind),
970+
};
971+
972+
let end = match end.try_to_value() {
973+
Some(end) => end.valtree.unwrap_leaf(),
974+
None => {
975+
let max_val = if is_signed {
976+
usable_size.signed_int_max() as u128
977+
} else {
978+
usable_size.unsigned_int_max()
979+
};
980+
ScalarInt::try_from_uint(max_val, usable_size).unwrap()
981+
}
982+
};
983+
let start = match start.try_to_value() {
984+
Some(start) => start.valtree.unwrap_leaf(),
985+
None => {
986+
let min_val = if is_signed {
987+
(usable_size.signed_int_min() as u128) & usable_size.unsigned_int_max()
988+
} else {
989+
0_u128
990+
};
991+
ScalarInt::try_from_uint(min_val, usable_size).unwrap()
992+
}
993+
};
994+
(is_signed, usable_size, start, end)
995+
}
996+
997+
match *pat {
998+
ty::PatternKind::NotNull => true,
999+
ty::PatternKind::Range { start, end } => {
1000+
let (is_signed, scalar_size, start, end) = unwrap_start_end(start, end);
1001+
let (scalar_min, scalar_max) = if is_signed {
1002+
(
1003+
(scalar_size.signed_int_min() as u128) & scalar_size.unsigned_int_max(),
1004+
scalar_size.signed_int_max() as u128,
1005+
)
1006+
} else {
1007+
(0, scalar_size.unsigned_int_max())
1008+
};
1009+
1010+
(start.to_bits(scalar_size), end.to_bits(scalar_size)) != (scalar_min, scalar_max)
1011+
}
1012+
ty::PatternKind::Or(patterns) => {
1013+
// first, get a simplified an sorted view of the ranges
1014+
let (is_signed, scalar_size, mut ranges) = {
1015+
let (is_signed, size, start, end) = match &*patterns[0] {
1016+
ty::PatternKind::Range { start, end } => unwrap_start_end(*start, *end),
1017+
ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"),
1018+
ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"),
1019+
};
1020+
(is_signed, size, vec![(start, end)])
1021+
};
1022+
let scalar_max = if is_signed {
1023+
scalar_size.signed_int_max() as u128
1024+
} else {
1025+
scalar_size.unsigned_int_max()
1026+
};
1027+
ranges.reserve(patterns.len() - 1);
1028+
for pat in patterns.iter().skip(1) {
1029+
match *pat {
1030+
ty::PatternKind::Range { start, end } => {
1031+
let (is_this_signed, this_scalar_size, start, end) =
1032+
unwrap_start_end(start, end);
1033+
assert_eq!(is_signed, is_this_signed);
1034+
assert_eq!(scalar_size, this_scalar_size);
1035+
ranges.push((start, end))
1036+
}
1037+
ty::PatternKind::Or(_) => bug!("recursive \"or\" patterns?"),
1038+
ty::PatternKind::NotNull => bug!("nonnull pattern in \"or\" pattern?"),
1039+
}
1040+
}
1041+
ranges.sort_by_key(|(start, _end)| {
1042+
let is_positive =
1043+
if is_signed { start.to_bits(scalar_size) <= scalar_max } else { true };
1044+
(is_positive, start.to_bits(scalar_size))
1045+
});
1046+
1047+
// then, range per range, look at the sizes of the gaps left in between
1048+
// (`prev_tail` is the highest value currently accounted for by the ranges,
1049+
// unless the first range has not been dealt with yet)
1050+
let mut prev_tail = scalar_max;
1051+
1052+
for (range_i, (start, end)) in ranges.into_iter().enumerate() {
1053+
let (start, end) = (start.to_bits(scalar_size), end.to_bits(scalar_size));
1054+
1055+
// if the start of the current range is lower
1056+
// than the current-highest-range-end, ...
1057+
let current_range_overlap =
1058+
if is_signed && prev_tail > scalar_max && start <= scalar_max {
1059+
false
1060+
} else if start <= u128::overflowing_add(prev_tail, 1).0 {
1061+
range_i > 0 // no overlap possible when dealing with the first range
1062+
} else {
1063+
false
1064+
};
1065+
if current_range_overlap {
1066+
// update the current-highest-range-end, if the current range has a higher end
1067+
if is_signed {
1068+
if prev_tail > scalar_max && end <= scalar_max {
1069+
prev_tail = end;
1070+
} else if prev_tail <= scalar_max && end > scalar_max {
1071+
// nothing to do here
1072+
} else {
1073+
// prev_tail and end have the same sign
1074+
prev_tail = u128::max(prev_tail, end)
1075+
}
1076+
} else {
1077+
// prev_tail and end have the same sign
1078+
prev_tail = u128::max(prev_tail, end)
1079+
}
1080+
} else {
1081+
// no range overlap: there are disallowed values
1082+
return true;
1083+
}
1084+
}
1085+
prev_tail != scalar_max
1086+
}
1087+
}
1088+
}
1089+
9371090
fn get_nullable_type_from_pat<'tcx>(
9381091
tcx: TyCtxt<'tcx>,
9391092
typing_env: ty::TypingEnv<'tcx>,

tests/ui/lint/improper-ctypes/lint-ctypes.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![feature(rustc_private)]
22
#![feature(extern_types)]
3+
#![feature(pattern_types, rustc_attrs)]
4+
#![feature(pattern_type_macro)]
35

46
#![allow(private_interfaces)]
57
#![deny(improper_ctypes)]
@@ -8,6 +10,7 @@ use std::cell::UnsafeCell;
810
use std::marker::PhantomData;
911
use std::ffi::{c_int, c_uint};
1012
use std::fmt::Debug;
13+
use std::pat::pattern_type;
1114

1215
unsafe extern "C" {type UnsizedOpaque;}
1316
trait Bar { }
@@ -72,6 +75,8 @@ extern "C" {
7275
pub fn box_type(p: Box<u32>);
7376
pub fn opt_box_type(p: Option<Box<u32>>);
7477
pub fn char_type(p: char); //~ ERROR uses type `char`
78+
pub fn pat_type1() -> Option<pattern_type!(u32 is 0..)>; //~ ERROR uses type `Option<(u32) is 0..>`
79+
pub fn pat_type2(p: Option<pattern_type!(u32 is 1..)>); // no error!
7580
pub fn trait_type(p: &dyn Bar); //~ ERROR uses type `&dyn Bar`
7681
pub fn tuple_type(p: (i32, i32)); //~ ERROR uses type `(i32, i32)`
7782
pub fn tuple_type2(p: I32Pair); //~ ERROR uses type `(i32, i32)`

0 commit comments

Comments
 (0)