Rollup merge of #136193 - oli-obk:pattern-type-ffi-checks, r=chenyukang
Implement pattern type ffi checks Previously we just rejected pattern types outright in FFI, but that was never meant to be a permanent situation. We'll need them supported to use them as the building block for `NonZero` and `NonNull` after all (both of which are FFI safe). best reviewed commit by commit.
This commit is contained in:
commit
2f3f83a4a3
8 changed files with 180 additions and 90 deletions
|
@ -390,9 +390,6 @@ lint_improper_ctypes_only_phantomdata = composed only of `PhantomData`
|
|||
|
||||
lint_improper_ctypes_opaque = opaque types have no C equivalent
|
||||
|
||||
lint_improper_ctypes_pat_help = consider using the base type instead
|
||||
|
||||
lint_improper_ctypes_pat_reason = pattern types have no C equivalent
|
||||
lint_improper_ctypes_slice_help = consider using a raw pointer instead
|
||||
|
||||
lint_improper_ctypes_slice_reason = slices have no C equivalent
|
||||
|
|
|
@ -241,10 +241,7 @@ fn structurally_same_type_impl<'tcx>(
|
|||
if let ty::Adt(def, args) = *ty.kind() {
|
||||
let is_transparent = def.repr().transparent();
|
||||
let is_non_null = types::nonnull_optimization_guaranteed(tcx, def);
|
||||
debug!(
|
||||
"non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}",
|
||||
ty, is_transparent, is_non_null
|
||||
);
|
||||
debug!(?ty, is_transparent, is_non_null);
|
||||
if is_transparent && !is_non_null {
|
||||
debug_assert_eq!(def.variants().len(), 1);
|
||||
let v = &def.variant(FIRST_VARIANT);
|
||||
|
@ -378,14 +375,14 @@ fn structurally_same_type_impl<'tcx>(
|
|||
|
||||
// An Adt and a primitive or pointer type. This can be FFI-safe if non-null
|
||||
// enum layout optimisation is being applied.
|
||||
(Adt(..), _) if is_primitive_or_pointer(b) => {
|
||||
(Adt(..) | Pat(..), _) if is_primitive_or_pointer(b) => {
|
||||
if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a, ckind) {
|
||||
a_inner == b
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(_, Adt(..)) if is_primitive_or_pointer(a) => {
|
||||
(_, Adt(..) | Pat(..)) if is_primitive_or_pointer(a) => {
|
||||
if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b, ckind) {
|
||||
b_inner == a
|
||||
} else {
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#![feature(let_chains)]
|
||||
#![feature(rustc_attrs)]
|
||||
#![feature(rustdoc_internals)]
|
||||
#![feature(try_blocks)]
|
||||
#![warn(unreachable_pub)]
|
||||
// tidy-alphabetical-end
|
||||
|
||||
|
|
|
@ -877,6 +877,37 @@ fn ty_is_known_nonnull<'tcx>(
|
|||
.filter_map(|variant| transparent_newtype_field(tcx, variant))
|
||||
.any(|field| ty_is_known_nonnull(tcx, typing_env, field.ty(tcx, args), mode))
|
||||
}
|
||||
ty::Pat(base, pat) => {
|
||||
ty_is_known_nonnull(tcx, typing_env, *base, mode)
|
||||
|| Option::unwrap_or_default(
|
||||
try {
|
||||
match **pat {
|
||||
ty::PatternKind::Range { start, end, include_end } => {
|
||||
match (start, end) {
|
||||
(Some(start), None) => {
|
||||
start.try_to_value()?.try_to_bits(tcx, typing_env)? > 0
|
||||
}
|
||||
(Some(start), Some(end)) => {
|
||||
let start =
|
||||
start.try_to_value()?.try_to_bits(tcx, typing_env)?;
|
||||
let end =
|
||||
end.try_to_value()?.try_to_bits(tcx, typing_env)?;
|
||||
|
||||
if include_end {
|
||||
// This also works for negative numbers, as we just need
|
||||
// to ensure we aren't wrapping over zero.
|
||||
start > 0 && end >= start
|
||||
} else {
|
||||
start > 0 && end > start
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -907,9 +938,8 @@ fn get_nullable_type<'tcx>(
|
|||
};
|
||||
return get_nullable_type(tcx, typing_env, inner_field_ty);
|
||||
}
|
||||
ty::Int(ty) => Ty::new_int(tcx, ty),
|
||||
ty::Uint(ty) => Ty::new_uint(tcx, ty),
|
||||
ty::RawPtr(ty, mutbl) => Ty::new_ptr(tcx, ty, mutbl),
|
||||
ty::Pat(base, ..) => return get_nullable_type(tcx, typing_env, base),
|
||||
ty::Int(_) | ty::Uint(_) | ty::RawPtr(..) => ty,
|
||||
// As these types are always non-null, the nullable equivalent of
|
||||
// `Option<T>` of these types are their raw pointer counterparts.
|
||||
ty::Ref(_region, ty, mutbl) => Ty::new_ptr(tcx, ty, mutbl),
|
||||
|
@ -965,63 +995,69 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
|
|||
ckind: CItemKind,
|
||||
) -> Option<Ty<'tcx>> {
|
||||
debug!("is_repr_nullable_ptr(tcx, ty = {:?})", ty);
|
||||
if let ty::Adt(ty_def, args) = ty.kind() {
|
||||
let field_ty = match &ty_def.variants().raw[..] {
|
||||
[var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) {
|
||||
([], [field]) | ([field], []) => field.ty(tcx, args),
|
||||
([field1], [field2]) => {
|
||||
let ty1 = field1.ty(tcx, args);
|
||||
let ty2 = field2.ty(tcx, args);
|
||||
match ty.kind() {
|
||||
ty::Adt(ty_def, args) => {
|
||||
let field_ty = match &ty_def.variants().raw[..] {
|
||||
[var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) {
|
||||
([], [field]) | ([field], []) => field.ty(tcx, args),
|
||||
([field1], [field2]) => {
|
||||
let ty1 = field1.ty(tcx, args);
|
||||
let ty2 = field2.ty(tcx, args);
|
||||
|
||||
if is_niche_optimization_candidate(tcx, typing_env, ty1) {
|
||||
ty2
|
||||
} else if is_niche_optimization_candidate(tcx, typing_env, ty2) {
|
||||
ty1
|
||||
} else {
|
||||
return None;
|
||||
if is_niche_optimization_candidate(tcx, typing_env, ty1) {
|
||||
ty2
|
||||
} else if is_niche_optimization_candidate(tcx, typing_env, ty2) {
|
||||
ty1
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if !ty_is_known_nonnull(tcx, typing_env, field_ty, ckind) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// At this point, the field's type is known to be nonnull and the parent enum is Option-like.
|
||||
// If the computed size for the field and the enum are different, the nonnull optimization isn't
|
||||
// being applied (and we've got a problem somewhere).
|
||||
let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, typing_env).ok();
|
||||
if !compute_size_skeleton(ty)?.same_size(compute_size_skeleton(field_ty)?) {
|
||||
bug!("improper_ctypes: Option nonnull optimization not applied?");
|
||||
}
|
||||
|
||||
// Return the nullable type this Option-like enum can be safely represented with.
|
||||
let field_ty_layout = tcx.layout_of(typing_env.as_query_input(field_ty));
|
||||
if field_ty_layout.is_err() && !field_ty.has_non_region_param() {
|
||||
bug!("should be able to compute the layout of non-polymorphic type");
|
||||
}
|
||||
|
||||
let field_ty_abi = &field_ty_layout.ok()?.backend_repr;
|
||||
if let BackendRepr::Scalar(field_ty_scalar) = field_ty_abi {
|
||||
match field_ty_scalar.valid_range(&tcx) {
|
||||
WrappingRange { start: 0, end }
|
||||
if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 =>
|
||||
{
|
||||
return Some(get_nullable_type(tcx, typing_env, field_ty).unwrap());
|
||||
}
|
||||
WrappingRange { start: 1, .. } => {
|
||||
return Some(get_nullable_type(tcx, typing_env, field_ty).unwrap());
|
||||
}
|
||||
WrappingRange { start, end } => {
|
||||
unreachable!("Unhandled start and end range: ({}, {})", start, end)
|
||||
}
|
||||
};
|
||||
|
||||
if !ty_is_known_nonnull(tcx, typing_env, field_ty, ckind) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// At this point, the field's type is known to be nonnull and the parent enum is Option-like.
|
||||
// If the computed size for the field and the enum are different, the nonnull optimization isn't
|
||||
// being applied (and we've got a problem somewhere).
|
||||
let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, typing_env).ok();
|
||||
if !compute_size_skeleton(ty)?.same_size(compute_size_skeleton(field_ty)?) {
|
||||
bug!("improper_ctypes: Option nonnull optimization not applied?");
|
||||
}
|
||||
|
||||
// Return the nullable type this Option-like enum can be safely represented with.
|
||||
let field_ty_layout = tcx.layout_of(typing_env.as_query_input(field_ty));
|
||||
if field_ty_layout.is_err() && !field_ty.has_non_region_param() {
|
||||
bug!("should be able to compute the layout of non-polymorphic type");
|
||||
}
|
||||
|
||||
let field_ty_abi = &field_ty_layout.ok()?.backend_repr;
|
||||
if let BackendRepr::Scalar(field_ty_scalar) = field_ty_abi {
|
||||
match field_ty_scalar.valid_range(&tcx) {
|
||||
WrappingRange { start: 0, end }
|
||||
if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 =>
|
||||
{
|
||||
return Some(get_nullable_type(tcx, typing_env, field_ty).unwrap());
|
||||
}
|
||||
WrappingRange { start: 1, .. } => {
|
||||
return Some(get_nullable_type(tcx, typing_env, field_ty).unwrap());
|
||||
}
|
||||
WrappingRange { start, end } => {
|
||||
unreachable!("Unhandled start and end range: ({}, {})", start, end)
|
||||
}
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
ty::Pat(base, pat) => match **pat {
|
||||
ty::PatternKind::Range { .. } => get_nullable_type(tcx, typing_env, *base),
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
||||
|
@ -1256,11 +1292,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
help: Some(fluent::lint_improper_ctypes_char_help),
|
||||
},
|
||||
|
||||
ty::Pat(..) => FfiUnsafe {
|
||||
ty,
|
||||
reason: fluent::lint_improper_ctypes_pat_reason,
|
||||
help: Some(fluent::lint_improper_ctypes_pat_help),
|
||||
},
|
||||
// It's just extra invariants on the type that you need to uphold,
|
||||
// but only the base type is relevant for being representable in FFI.
|
||||
ty::Pat(base, ..) => self.check_type_for_ffi(acc, base),
|
||||
|
||||
ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => {
|
||||
FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue