Skip to content

lint ImproperCTypes: overhaul (take 2 of "better handling of indirections") #134697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
307a134
Changes to the ImproperCTypes lint, start of take 1 [does not pass te…
niacdoial Dec 11, 2024
999972b
lint ImproperCTypes: make checking indirection more DRY
niacdoial Dec 23, 2024
ca5fb8f
lint ImproperCTypes: fix TypeSizedness code
niacdoial Dec 23, 2024
f3dbc00
lint ImproperCTypes: add test to cover all ty_kinds
niacdoial Dec 23, 2024
6027630
lint ImproperCtypes: move code to new place, prior to full rewrite
niacdoial Dec 29, 2024
e4ec443
lint ImproperCTypes: properly separate the lint generation from the type
niacdoial Jan 5, 2025
366f8f2
lint ImproperCTypes: more cleanups and reworks [...]
niacdoial Jan 9, 2025
b27bb61
lint ImproperCTypes: add considerations for which values can be sourc…
niacdoial Jan 18, 2025
4aa6a20
lint ImproperCTypes: smoothing out some nits and un-tidy-ness
niacdoial Jan 18, 2025
f794b6d
lint ImproperCTypes: add recursion limit
niacdoial May 23, 2025
e842f07
lint ImproperCTypes: split the two lints into four total [...]
niacdoial May 4, 2025
b73c51f
lint ImproperCTypes: deal with uninhabited types
niacdoial May 6, 2025
9dfb1d0
lint ImproperCTypes: redo handling of pattern types [...]
niacdoial May 6, 2025
83cee26
lint ImproperCTypes: change what elements are being checked [...]
niacdoial May 6, 2025
13030e3
lint ImproperCTypes: rm improper_ctype_definitions [...]
niacdoial May 6, 2025
43bfc72
lint ImproperCTypes: clean up exported static variables
niacdoial May 20, 2025
b70eb45
lint ImproperCTypes: changes to pass CI [...]
niacdoial May 22, 2025
cecee60
lint ImproperCTypes: remove the parts about checking value assumptions
niacdoial Jun 4, 2025
c734436
lint ImproperCTypes: misc. changes due to review
niacdoial Jun 5, 2025
2821fb5
TEMP COMMIT, to be deleted on rebase: remove i128/u128 warnings
niacdoial Jun 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4011,6 +4011,7 @@ dependencies = [
"rustc_span",
"rustc_target",
"rustc_trait_selection",
"rustc_type_ir",
"smallvec",
"tracing",
"unicode-security",
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_cranelift/example/std_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ fn rust_call_abi() {
struct I64X2([i64; 2]);

#[cfg_attr(target_arch = "s390x", allow(dead_code))]
#[allow(improper_ctypes_definitions)]
#[allow(improper_c_fn_definitions)]
extern "C" fn foo(_a: I64X2) {}

#[cfg(target_arch = "x86_64")]
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_codegen_llvm/src/back/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,10 +462,11 @@ fn report_inline_asm(
cgcx.diag_emitter.inline_asm_error(span, msg, level, source);
}

unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void) {
unsafe extern "C" fn diagnostic_handler(info: Option<&DiagnosticInfo>, user: *mut c_void) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with having to wrap all references in Option. You have the invariant that the reference is valid (if not NULL) either way. It is very much reasonable to expect the C side not to pass NULL, in which case the argument will never be None.

if user.is_null() {
return;
}
let info = info.unwrap();
let (cgcx, dcx) =
unsafe { *(user as *const (&CodegenContext<LlvmCodegenBackend>, DiagCtxtHandle<'_>)) };

Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,8 @@ unsafe extern "C" {
pub(crate) type DiagnosticHandler;
}

pub(crate) type DiagnosticHandlerTy = unsafe extern "C" fn(&DiagnosticInfo, *mut c_void);
// the first argument is not supposed to be nullable, let's still `unwrap()` that
pub(crate) type DiagnosticHandlerTy = unsafe extern "C" fn(Option<&DiagnosticInfo>, *mut c_void);

pub(crate) mod debuginfo {
use std::ptr;
Expand Down Expand Up @@ -1972,12 +1973,12 @@ unsafe extern "C" {
pub(crate) fn LLVMRustBuildMinNum<'a>(
B: &Builder<'a>,
LHS: &'a Value,
LHS: &'a Value,
RHS: &'a Value,
) -> &'a Value;
pub(crate) fn LLVMRustBuildMaxNum<'a>(
B: &Builder<'a>,
LHS: &'a Value,
LHS: &'a Value,
RHS: &'a Value,
) -> &'a Value;

// Atomic Operations
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
rustc_target = { path = "../rustc_target" }
rustc_trait_selection = { path = "../rustc_trait_selection" }
rustc_type_ir = { path = "../rustc_type_ir" }
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
tracing = "0.1"
unicode-security = "0.1.0"
Expand Down
53 changes: 35 additions & 18 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -370,23 +370,29 @@ lint_implicit_unsafe_autorefs = implicit autoref creates a reference to the dere
.method_def = method calls to `{$method_name}` require a reference
.suggestion = try using a raw pointer method instead; or if this reference is intentional, make it explicit

lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe
lint_improper_ctypes = {$desc} uses type `{$ty}`, which is not FFI-safe
.label = not FFI-safe
.note = the type is defined here

lint_improper_ctypes_128bit = 128-bit integers don't currently have a known stable ABI

lint_improper_ctypes_array_help = consider passing a pointer to the array

lint_improper_ctypes_array_reason = passing raw arrays by value is not FFI-safe
lint_improper_ctypes_box = box cannot be represented as a single pointer

lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead

lint_improper_ctypes_char_reason = the `char` type has no C equivalent

lint_improper_ctypes_cstr_help =
consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()`
lint_improper_ctypes_cstr_help_const =
consider passing a `*const std::ffi::c_char` instead, and use `CStr::as_ptr()` or `CString::as_ptr()`
lint_improper_ctypes_cstr_help_mut =
consider passing a `*mut std::ffi::c_char` instead, and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
lint_improper_ctypes_cstr_help_owned =
consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
and use `CString::into_raw()` then `CString::from_raw()` or a dedicated buffer
(note that `CString::into_raw()`'s output must not be `libc::free()`'d)
lint_improper_ctypes_cstr_help_unknown =
consider passing a `*const std::ffi::c_char` or `*mut std::ffi::c_char` instead,
and use (depending on the use case) `CStr::as_ptr()`, `CString::into_raw()` then `CString::from_raw()`, or a dedicated buffer
lint_improper_ctypes_cstr_reason = `CStr`/`CString` do not have a guaranteed layout

lint_improper_ctypes_dyn = trait objects have no C equivalent
Expand All @@ -397,40 +403,51 @@ lint_improper_ctypes_enum_repr_help =
lint_improper_ctypes_enum_repr_reason = enum has no representation hint
lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead

lint_improper_ctypes_fnptr_indirect_reason = the function pointer to `{$ty}` is FFI-unsafe due to `{$inner_ty}`
lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention

lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive
lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants

lint_improper_ctypes_only_phantomdata = composed only of `PhantomData`

lint_improper_ctypes_opaque = opaque types have no C equivalent

lint_improper_ctypes_slice_help = consider using a raw pointer instead

lint_improper_ctypes_slice_help = consider using a raw pointer to the slice's first element (and a length) instead
lint_improper_ctypes_slice_reason = slices have no C equivalent
lint_improper_ctypes_str_help = consider using `*const u8` and a length instead

lint_improper_ctypes_str_help = consider using `*const u8` and a length instead
lint_improper_ctypes_str_reason = string slices have no C equivalent
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct

lint_improper_ctypes_struct_fieldless_reason = this struct has no fields
lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
lint_improper_ctypes_struct_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
lint_improper_ctypes_struct_dueto = this struct/enum/union (`{$ty}`) is FFI-unsafe due to a `{$inner_ty}` field

lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout
lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive
lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields
lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct
lint_improper_ctypes_struct_fieldless_reason = `{$ty}` has no fields

lint_improper_ctypes_tuple_help = consider using a struct instead
lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` (not `#[repr(C,packed)]`) or `#[repr(transparent)]` attribute to `{$ty}`
lint_improper_ctypes_struct_layout_reason = `{$ty}` has unspecified layout
lint_improper_ctypes_struct_non_exhaustive = `{$ty}` is non-exhaustive
lint_improper_ctypes_struct_zst = `{$ty}` contains only zero-sized fields

lint_improper_ctypes_tuple_help = consider using a struct instead
lint_improper_ctypes_tuple_reason = tuples have unspecified layout
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union

lint_improper_ctypes_uninhabited_enum = zero-variant enums and other uninhabited types are not allowed in function arguments and static variables
lint_improper_ctypes_uninhabited_never = the never type (`!`) and other uninhabited types are not allowed in function arguments and static variables

lint_improper_ctypes_union_consider_transparent = `{$ty}` has exactly one non-zero-sized field, consider making it `#[repr(transparent)]` instead
lint_improper_ctypes_union_fieldless_help = consider adding a member to this union
lint_improper_ctypes_union_fieldless_reason = this union has no fields
lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union
lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` attribute to this union

lint_improper_ctypes_union_layout_reason = this union has unspecified layout
lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive

lint_improper_ctypes_unsized_box = this box for an unsized type contains metadata, which makes it incompatible with a C pointer
lint_improper_ctypes_unsized_ptr = this pointer to an unsized type contains metadata, which makes it incompatible with a C pointer
lint_improper_ctypes_unsized_ref = this reference to an unsized type contains metadata, which makes it incompatible with a C pointer

lint_incomplete_include =
include macro expected single expression in source

Expand Down
27 changes: 8 additions & 19 deletions compiler/rustc_lint/src/foreign_modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ impl ClashingExternDeclarations {
ty::TypingEnv::non_body_analysis(tcx, this_fi.owner_id),
existing_decl_ty,
this_decl_ty,
types::CItemKind::Declaration,
) {
let orig = name_of_extern_decl(tcx, existing_did);

Expand Down Expand Up @@ -208,10 +207,9 @@ fn structurally_same_type<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
a: Ty<'tcx>,
b: Ty<'tcx>,
ckind: types::CItemKind,
) -> bool {
let mut seen_types = UnordSet::default();
let result = structurally_same_type_impl(&mut seen_types, tcx, typing_env, a, b, ckind);
let result = structurally_same_type_impl(&mut seen_types, tcx, typing_env, a, b);
if cfg!(debug_assertions) && result {
// Sanity-check: must have same ABI, size and alignment.
// `extern` blocks cannot be generic, so we'll always get a layout here.
Expand All @@ -230,7 +228,6 @@ fn structurally_same_type_impl<'tcx>(
typing_env: ty::TypingEnv<'tcx>,
a: Ty<'tcx>,
b: Ty<'tcx>,
ckind: types::CItemKind,
) -> bool {
debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b);

Expand Down Expand Up @@ -301,33 +298,26 @@ fn structurally_same_type_impl<'tcx>(
typing_env,
tcx.type_of(a_did).instantiate(tcx, a_gen_args),
tcx.type_of(b_did).instantiate(tcx, b_gen_args),
ckind,
)
},
)
}
(ty::Array(a_ty, a_len), ty::Array(b_ty, b_len)) => {
// For arrays, we also check the length.
a_len == b_len
&& structurally_same_type_impl(
seen_types, tcx, typing_env, *a_ty, *b_ty, ckind,
)
&& structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::Slice(a_ty), ty::Slice(b_ty)) => {
structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty, ckind)
structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::RawPtr(a_ty, a_mutbl), ty::RawPtr(b_ty, b_mutbl)) => {
a_mutbl == b_mutbl
&& structurally_same_type_impl(
seen_types, tcx, typing_env, *a_ty, *b_ty, ckind,
)
&& structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::Ref(_a_region, a_ty, a_mut), ty::Ref(_b_region, b_ty, b_mut)) => {
// For structural sameness, we don't need the region to be same.
a_mut == b_mut
&& structurally_same_type_impl(
seen_types, tcx, typing_env, *a_ty, *b_ty, ckind,
)
&& structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty)
}
(ty::FnDef(..), ty::FnDef(..)) => {
let a_poly_sig = a.fn_sig(tcx);
Expand All @@ -341,15 +331,14 @@ fn structurally_same_type_impl<'tcx>(
(a_sig.abi, a_sig.safety, a_sig.c_variadic)
== (b_sig.abi, b_sig.safety, b_sig.c_variadic)
&& a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
structurally_same_type_impl(seen_types, tcx, typing_env, *a, *b, ckind)
structurally_same_type_impl(seen_types, tcx, typing_env, *a, *b)
})
&& structurally_same_type_impl(
seen_types,
tcx,
typing_env,
a_sig.output(),
b_sig.output(),
ckind,
)
}
(ty::Tuple(..), ty::Tuple(..)) => {
Expand Down Expand Up @@ -377,14 +366,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.
(ty::Adt(..) | ty::Pat(..), _) if is_primitive_or_pointer(b) => {
if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a, ckind) {
if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a) {
a_inner == b
} else {
false
}
}
(_, ty::Adt(..) | ty::Pat(..)) if is_primitive_or_pointer(a) => {
if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b, ckind) {
if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b) {
b_inner == a
} else {
false
Expand Down
12 changes: 10 additions & 2 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,7 @@ late_lint_methods!(
DefaultCouldBeDerived: DefaultCouldBeDerived::default(),
DerefIntoDynSupertrait: DerefIntoDynSupertrait,
DropForgetUseless: DropForgetUseless,
ImproperCTypesDeclarations: ImproperCTypesDeclarations,
ImproperCTypesDefinitions: ImproperCTypesDefinitions,
ImproperCTypesLint: ImproperCTypesLint,
InvalidFromUtf8: InvalidFromUtf8,
VariantSizeDifferences: VariantSizeDifferences,
PathStatements: PathStatements,
Expand Down Expand Up @@ -338,6 +337,14 @@ fn register_builtins(store: &mut LintStore) {
REFINING_IMPL_TRAIT_INTERNAL
);

add_lint_group!(
"improper_c_boundaries",
IMPROPER_C_CALLBACKS,
IMPROPER_C_FN_DEFINITIONS,
IMPROPER_C_VAR_DEFINITIONS,
IMPROPER_CTYPES
);

add_lint_group!("deprecated_safe", DEPRECATED_SAFE_2024);

// Register renamed and removed lints.
Expand All @@ -356,6 +363,7 @@ fn register_builtins(store: &mut LintStore) {
store.register_renamed("unused_tuple_struct_fields", "dead_code");
store.register_renamed("static_mut_ref", "static_mut_refs");
store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries");
store.register_renamed("improper_ctypes_definitions", "improper_c_fn_definitions");

// These were moved to tool lints, but rustc still sees them when compiling normally, before
// tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use
Expand Down
43 changes: 32 additions & 11 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1940,29 +1940,50 @@ pub(crate) enum UnpredictableFunctionPointerComparisonsSuggestion<'a, 'tcx> {
},
}

pub(crate) struct ImproperCTypesLayer<'a> {
pub ty: Ty<'a>,
pub inner_ty: Option<Ty<'a>>,
pub note: DiagMessage,
pub span_note: Option<Span>,
pub help: Option<DiagMessage>,
}

impl<'a> Subdiagnostic for ImproperCTypesLayer<'a> {
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
diag.arg("ty", self.ty);
if let Some(ty) = self.inner_ty {
diag.arg("inner_ty", ty);
}

if let Some(help) = self.help {
diag.help(diag.eagerly_translate(help));
}

diag.note(diag.eagerly_translate(self.note));
if let Some(note) = self.span_note {
diag.span_note(note, fluent::lint_note);
};
}
}

pub(crate) struct ImproperCTypes<'a> {
pub ty: Ty<'a>,
pub desc: &'a str,
pub label: Span,
pub help: Option<DiagMessage>,
pub note: DiagMessage,
pub span_note: Option<Span>,
pub reasons: Vec<ImproperCTypesLayer<'a>>,
}

// Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span>
impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> {
fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
diag.primary_message(fluent::lint_improper_ctypes);
diag.arg("ty", self.ty);
diag.arg("desc", self.desc);
diag.span_label(self.label, fluent::lint_label);
if let Some(help) = self.help {
diag.help(help);
}
diag.note(self.note);
if let Some(note) = self.span_note {
diag.span_note(note, fluent::lint_note);
for reason in self.reasons.into_iter() {
diag.subdiagnostic(reason);
}
// declare the arguments at the end to avoid them being clobbered in the subdiagnostics
diag.arg("ty", self.ty);
diag.arg("desc", self.desc);
}
}

Expand Down
Loading