Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ language_item_table! {
OrderingEnum, sym::Ordering, ordering_enum, Target::Enum, GenericRequirement::Exact(0);
PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1);
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None;
CVoid, sym::c_void, c_void, Target::Struct, GenericRequirement::None;

Type, sym::type_info, type_struct, Target::Struct, GenericRequirement::None;
TypeId, sym::type_id, type_id, Target::Struct, GenericRequirement::None;
Expand Down
63 changes: 31 additions & 32 deletions library/core/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#![stable(feature = "core_ffi", since = "1.30.0")]
#![allow(non_camel_case_types)]

use crate::fmt;
use crate::panic::RefUnwindSafe;
#[stable(feature = "c_str_module", since = "1.88.0")]
pub mod c_str;
#[doc(inline)]
#[stable(feature = "core_c_str", since = "1.64.0")]
pub use self::c_str::CStr;
Expand All @@ -18,14 +22,6 @@ pub use self::c_str::FromBytesUntilNulError;
#[doc(inline)]
#[stable(feature = "core_c_str", since = "1.64.0")]
pub use self::c_str::FromBytesWithNulError;
use crate::fmt;

#[stable(feature = "c_str_module", since = "1.88.0")]
pub mod c_str;

mod va_list;
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
pub use self::va_list::{VaArgSafe, VaList};

mod primitives;
#[stable(feature = "core_ffi_c", since = "1.64.0")]
Expand All @@ -36,35 +32,38 @@ pub use self::primitives::{
#[unstable(feature = "c_size_t", issue = "88345")]
pub use self::primitives::{c_ptrdiff_t, c_size_t, c_ssize_t};

// N.B., for LLVM to recognize the void pointer type and by extension
// functions like malloc(), we need to have it represented as i8* in
// LLVM bitcode. The enum used here ensures this and prevents misuse
// of the "raw" type by only having private variants. We need two
// variants, because the compiler complains about the repr attribute
// otherwise and we need at least one variant as otherwise the enum
// would be uninhabited and at least dereferencing such pointers would
// be UB.
mod va_list;
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
pub use self::va_list::{VaArgSafe, VaList};

#[doc = include_str!("c_void.md")]
#[lang = "c_void"]
#[repr(u8)]
#[repr(transparent)]
#[stable(feature = "core_c_void", since = "1.30.0")]
pub enum c_void {
#[unstable(
feature = "c_void_variant",
reason = "temporary implementation detail",
issue = "none"
)]
#[doc(hidden)]
__variant1,
#[unstable(
feature = "c_void_variant",
reason = "temporary implementation detail",
issue = "none"
)]
#[doc(hidden)]
__variant2,
pub struct c_void {

@Darksonn Darksonn Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This changes the doc path for this type from core/ffi/enum.c_void.html to core/ffi/struct.c_void.html. If we make this change, we should introduce a redirect to keep links to the old path working.

View changes since the review

@Darksonn Darksonn Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

mejrs makes a good point on zulip

It's also a breaking change to change it back from a struct to enum if we ever need to, even if all fields are private you can make a c_void { .. } pattern

Let's add a test using this pattern so that we catch it when/if this changes. (Even if we don't make this change, we should add the test.)

View changes since the review

@Jules-Bertholet Jules-Bertholet Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If you are doing this, that means you are matching on a value or reference to c_void, which means your code is essentially guaranteed to be wrong (UB or near-UB).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not necessarily. The code might just be dead code.

// Using this weird type ensures a size of 1,
// while minimizing UB if a user incorrectly tries
// to dereference a pointer to `c_void`,
// or reborrow it as a reference.
#[cfg(not(miri))]
_inner: crate::pin::UnsafePinned<crate::mem::MaybeUninit<u8>>,

// However, if running in Miri,
// we want to maximize detection of UB,
// so we make `c_void` uninhabited.
Comment on lines +50 to +52

@RalfJung RalfJung Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think this is worth the risk of divergence from the behavior during regular compilation.

View changes since the review

@Jules-Bertholet Jules-Bertholet Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Compare the risks of not doing this. Either we:

  • Use the permissive definition everywhere, so Miri stops flagging misuse that happens in practice and that it currently flags, and such misuse becomes more common
  • Use the strict definition everywhere, so code that in the past was UB or almost-UB but happened to work, now triggers real nasal demons
  • Stick with the current fragile middle ground, where there is a bunch of almost-UB—which Miri won't flag, potentially giving people false confidence, but it's very fragile and easy to turn into full UB

I think the theoretical downside of us maybe having overlooked some way in which the cfg(miri) version has less UB, is outweighed by these practical and certain downsides.

@RalfJung RalfJung Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Miri is not a general tool to detect misuse. It is a very specific tool to detect language UB. Whatever misuse they do is apparently not language UB, so Miri is the wrong tool to find it. I am fine with having extra checks under if cfg!(miri) to detect bugs early (though usually those should then probably also be enabled as debug assertions), as those are obviously correct. But I am not fine with using entirely different type definitions under cfg!(miri).

I understand your arguments in favor of this change, but I don't think they are convincing enough. There are way too many things that happen inside the compiler that may go different for those different type definitions, that's just not a game I am willing to play. That's still a clear "thanks but no" from my side for any cfg!(miri) in this type definition.

@Jules-Bertholet Jules-Bertholet Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Whatever misuse they do is apparently not language UB

Plenty is! And plenty isn't only because of unspecified details of the current layout.

If you don't like the approach this PR takes, which of the alternatives I listed would you prefer?

@RalfJung RalfJung Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that's up to libs or libs-api to decide, I don't have a strong opinion beyond "Miri should check the actual code we usually run".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It needs to mock the C side, but the Rust side should be faithful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How would you feel about making the cfg(miri) definition identical to the cfg(not(miri)), except for an additional !-typed field?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's still far from identical as such a field makes quite the difference for layout computation.

Might might do ill-advised nonsense like Option<c_void> and whatever. I don't want that to start doing things in Miri that it wouldn't do outside Miri.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And we can't just make it straight-up uninhabited always, right? Because that breaks other stuff/makes it incorrect? (I haven't thought about this long enough to remember the answer, it's out of cache apparently.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because we don't want to actually start triggering nasal demons in the (many) crates that incorrectly do this, but by chance narrowly avoid UB, or trigger only "benign" UB

#[cfg(miri)]
_inner: u8,
#[cfg(miri)]
_uninhabited: !,
// Ensure no `Freeze`, for consistency with `not(miri)`
#[cfg(miri)]
_nonfreeze: crate::cell::SyncUnsafeCell<()>,
}

// for backward compatibility.
#[stable(feature = "core_c_void", since = "1.30.0")]
impl RefUnwindSafe for c_void {}
Comment on lines +57 to +59

@Darksonn Darksonn Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This PR removes the following trait implementations:

  • Freeze
  • Unpin
  • UnsafeUnpin

One of these is a stable trait, so this is a breaking change. Should we also implement Unpin?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, that was an oversight, good catch


#[stable(feature = "std_debug", since = "1.16.0")]
impl fmt::Debug for c_void {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand Down
5 changes: 0 additions & 5 deletions src/doc/unstable-book/src/library-features/c-void-variant.md

This file was deleted.

2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/native_lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ pub fn build_libffi_closure<'tcx, 'this>(
/// As future improvement we might continue execution in the interpreter here.
unsafe extern "C" fn libffi_closure_callback<'tcx>(
_cif: &libffi::low::ffi_cif,
_result: &mut c_void,
_result: &mut (),
_args: *const *const c_void,
data: &LibffiClosureData<'tcx>,
) {
Expand Down
7 changes: 7 additions & 0 deletions src/tools/miri/tests/fail/validity/c-void-validity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use core::ffi::c_void;

fn main() {
let mut b: u8 = 0;
let p: *const c_void = (&raw mut b).cast_const().cast();
let _r: &c_void = unsafe { &*p }; //~ERROR: constructing invalid value
}
13 changes: 13 additions & 0 deletions src/tools/miri/tests/fail/validity/c-void-validity.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error: Undefined Behavior: constructing invalid value of type &std::ffi::c_void: encountered a reference pointing to uninhabited type `std::ffi::c_void`
--> tests/fail/validity/c-void-validity.rs:LL:CC
|
LL | let _r: &c_void = unsafe { &*p };
| ^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

12 changes: 0 additions & 12 deletions src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs

@ShoyuVanilla ShoyuVanilla Jul 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We auto-generate this file with rust-lang/rust-analyzer repository's CI job. Could you remove this change?

View changes since the review

Original file line number Diff line number Diff line change
Expand Up @@ -4356,18 +4356,6 @@ The tracking issue for this feature is: [#148767]

[#148767]: https://github.com/rust-lang/rust/issues/148767

------------------------
"##,
default_severity: Severity::Allow,
warn_since: None,
deny_since: None,
},
Lint {
label: "c_void_variant",
description: r##"# `c_void_variant`

This feature is internal to the Rust compiler and is not intended for general use.

------------------------
"##,
default_severity: Severity::Allow,
Expand Down
9 changes: 5 additions & 4 deletions tests/auxiliary/minicore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
auto_traits,
freeze_impls,
negative_impls,
never_type,
pattern_types,
rustc_attrs,
decl_macro,
Expand Down Expand Up @@ -392,10 +393,10 @@ pub mod hint {
}

#[lang = "c_void"]
#[repr(u8)]
pub enum c_void {
__variant1,
__variant2,
#[repr(transparent)]
pub struct c_void {
_inner: u8,
_uninhabited: !,
}

#[rustc_builtin_macro(pattern_type)]
Expand Down
Loading