about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <476013+matthiaskrgr@users.noreply.github.com>2025-07-20 08:56:05 +0200
committerGitHub <noreply@github.com>2025-07-20 08:56:05 +0200
commit6d7d366fd3c1ae6ce722e46d1fecfdf258d02f9d (patch)
tree8d13623ca86e733418a5009fe6751f91150f4702
parent6707bf0f59485cf054ac1095725df43220e4be20 (diff)
parent8a8717e971dbdc6155506a4332e9ce8ef9151caa (diff)
downloadrust-6d7d366fd3c1ae6ce722e46d1fecfdf258d02f9d.tar.gz
rust-6d7d366fd3c1ae6ce722e46d1fecfdf258d02f9d.zip
Rollup merge of #141260 - LuigiPiucco:volatile-null, r=RalfJung
Allow volatile access to non-Rust memory, including address 0

This PR relaxes the `ub_check` in the `read_volatile`/`write_volatile` pointer operations to allow passing null. This is needed to support processors which hard-code peripheral registers on address 0, like the AVR chip ATtiny1626. LLVM understands this as valid and handles it correctly, as tested in my [PR to add a note about it](https://github.com/llvm/llvm-project/pull/139803/commits/6387c82255c56d3035d249eb54110695e76b8030#diff-81bbb96298c32fa901beb82ab3b97add27a410c01d577c1f8c01000ed2055826) (rustc generates the same LLVM IR as expected there when this PR is applied, and consequently the same AVR assembly).

Follow-up and implementation of the discussions in:
- https://internals.rust-lang.org/t/pre-rfc-conditionally-supported-volatile-access-to-address-0/12881/7
- https://github.com/Rahix/avr-device/pull/185;
- [#t-lang > Adding the possibility of volatile access to address 0](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/Adding.20the.20possibility.20of.20volatile.20access.20to.20address.200/with/513303502)
- https://discourse.llvm.org/t/rfc-volatile-access-to-non-dereferenceable-memory-may-be-well-defined/86303

r? ````@RalfJung````

Also fixes https://github.com/rust-lang/unsafe-code-guidelines/issues/29 (about as good as it'll get, null will likely never be a "normal" address in Rust)
-rw-r--r--compiler/rustc_lint/src/ptr_nulls.rs4
-rw-r--r--library/core/src/ptr/mod.rs168
-rw-r--r--library/core/src/ub_checks.rs18
-rw-r--r--tests/ui/lint/invalid_null_args.rs5
-rw-r--r--tests/ui/lint/invalid_null_args.stderr58
-rw-r--r--tests/ui/precondition-checks/read_volatile.rs6
-rw-r--r--tests/ui/precondition-checks/write_volatile.rs6
7 files changed, 127 insertions, 138 deletions
diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs
index 826bce2c315..b2fa0fba76d 100644
--- a/compiler/rustc_lint/src/ptr_nulls.rs
+++ b/compiler/rustc_lint/src/ptr_nulls.rs
@@ -160,12 +160,10 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
                 let (arg_indices, are_zsts_allowed): (&[_], _) = match diag_name {
                     sym::ptr_read
                     | sym::ptr_read_unaligned
-                    | sym::ptr_read_volatile
                     | sym::ptr_replace
                     | sym::ptr_write
                     | sym::ptr_write_bytes
-                    | sym::ptr_write_unaligned
-                    | sym::ptr_write_volatile => (&[0], true),
+                    | sym::ptr_write_unaligned => (&[0], true),
                     sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut => (&[0], false),
                     sym::ptr_copy
                     | sym::ptr_copy_nonoverlapping
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index fe8c6f83034..dbe3999b4a4 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -28,7 +28,8 @@
 //!   undefined behavior to perform two concurrent accesses to the same location from different
 //!   threads unless both accesses only read from memory. Notice that this explicitly
 //!   includes [`read_volatile`] and [`write_volatile`]: Volatile accesses cannot
-//!   be used for inter-thread synchronization.
+//!   be used for inter-thread synchronization, regardless of whether they are acting on
+//!   Rust memory or not.
 //! * The result of casting a reference to a pointer is valid for as long as the
 //!   underlying allocation is live and no reference (just raw pointers) is used to
 //!   access the same memory. That is, reference and pointer accesses cannot be
@@ -114,6 +115,10 @@
 //! fully contiguous (i.e., has no "holes"), there is no guarantee that this
 //! will not change in the future.
 //!
+//! Allocations must behave like "normal" memory: in particular, reads must not have
+//! side-effects, and writes must become visible to other threads using the usual synchronization
+//! primitives.
+//!
 //! For any allocation with `base` address, `size`, and a set of
 //! `addresses`, the following are guaranteed:
 //! - For all addresses `a` in `addresses`, `a` is in the range `base .. (base +
@@ -2021,54 +2026,61 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
     }
 }
 
-/// Performs a volatile read of the value from `src` without moving it. This
-/// leaves the memory in `src` unchanged.
-///
-/// Volatile operations are intended to act on I/O memory, and are guaranteed
-/// to not be elided or reordered by the compiler across other volatile
-/// operations.
-///
-/// # Notes
-///
-/// Rust does not currently have a rigorously and formally defined memory model,
-/// so the precise semantics of what "volatile" means here is subject to change
-/// over time. That being said, the semantics will almost always end up pretty
-/// similar to [C11's definition of volatile][c11].
-///
-/// The compiler shouldn't change the relative order or number of volatile
-/// memory operations. However, volatile memory operations on zero-sized types
-/// (e.g., if a zero-sized type is passed to `read_volatile`) are noops
-/// and may be ignored.
-///
-/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
+/// Performs a volatile read of the value from `src` without moving it.
+///
+/// Volatile operations are intended to act on I/O memory. As such, they are considered externally
+/// observable events (just like syscalls, but less opaque), and are guaranteed to not be elided or
+/// reordered by the compiler across other externally observable events. With this in mind, there
+/// are two cases of usage that need to be distinguished:
+///
+/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like
+///   [`read`], except for the additional guarantee that it won't be elided or reordered (see
+///   above). This implies that the operation will actually access memory and not e.g. be lowered to
+///   reusing data from a previous read. Other than that, all the usual rules for memory accesses
+///   apply (including provenance).  In particular, just like in C, whether an operation is volatile
+///   has no bearing whatsoever on questions involving concurrent accesses from multiple threads.
+///   Volatile accesses behave exactly like non-atomic accesses in that regard.
+///
+/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
+///   allocation. In this use-case, the pointer does *not* have to be [valid] for reads. This is
+///   typically used for CPU and peripheral registers that must be accessed via an I/O memory
+///   mapping, most commonly at fixed addresses reserved by the hardware. These often have special
+///   semantics associated to their manipulation, and cannot be used as general purpose memory.
+///   Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
+///   of such a read are well-defined by the target hardware. The provenance of the pointer is
+///   irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
+///   can cause side-effects, but those must not affect Rust-allocated memory in any way. This
+///   access is still not considered [atomic], and as such it cannot be used for inter-thread
+///   synchronization.
+///
+/// Note that volatile memory operations where T is a zero-sized type are noops and may be ignored.
+///
+/// [allocation]: crate::ptr#allocated-object
+/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
 ///
 /// # Safety
 ///
+/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of whether `T` is
+/// [`Copy`]. If `T` is not [`Copy`], using both the returned value and the value at `*src` can
+/// [violate memory safety][read-ownership]. However, storing non-[`Copy`] types in volatile memory
+/// is almost certainly incorrect.
+///
 /// Behavior is undefined if any of the following conditions are violated:
 ///
-/// * `src` must be [valid] for reads.
+/// * `src` must be either [valid] for reads, or it must point to memory outside of all Rust
+///   allocations and reading from that memory must:
+///   - not trap, and
+///   - not cause any memory inside a Rust allocation to be modified.
 ///
 /// * `src` must be properly aligned.
 ///
-/// * `src` must point to a properly initialized value of type `T`.
-///
-/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of
-/// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned
-/// value and the value at `*src` can [violate memory safety][read-ownership].
-/// However, storing non-[`Copy`] types in volatile memory is almost certainly
-/// incorrect.
+/// * Reading from `src` must produce a properly initialized value of type `T`.
 ///
 /// Note that even if `T` has size `0`, the pointer must be properly aligned.
 ///
 /// [valid]: self#safety
 /// [read-ownership]: read#ownership-of-the-returned-value
 ///
-/// Just like in C, whether an operation is volatile has no bearing whatsoever
-/// on questions involving concurrent access from multiple threads. Volatile
-/// accesses behave exactly like non-atomic accesses in that regard. In particular,
-/// a race between a `read_volatile` and any write operation to the same location
-/// is undefined behavior.
-///
 /// # Examples
 ///
 /// Basic usage:
@@ -2090,50 +2102,63 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
     unsafe {
         ub_checks::assert_unsafe_precondition!(
             check_language_ub,
-            "ptr::read_volatile requires that the pointer argument is aligned and non-null",
+            "ptr::read_volatile requires that the pointer argument is aligned",
             (
                 addr: *const () = src as *const (),
                 align: usize = align_of::<T>(),
-                is_zst: bool = T::IS_ZST,
-            ) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
+            ) => ub_checks::maybe_is_aligned(addr, align)
         );
         intrinsics::volatile_load(src)
     }
 }
 
-/// Performs a volatile write of a memory location with the given value without
-/// reading or dropping the old value.
-///
-/// Volatile operations are intended to act on I/O memory, and are guaranteed
-/// to not be elided or reordered by the compiler across other volatile
-/// operations.
-///
-/// `write_volatile` does not drop the contents of `dst`. This is safe, but it
-/// could leak allocations or resources, so care should be taken not to overwrite
-/// an object that should be dropped.
-///
-/// Additionally, it does not drop `src`. Semantically, `src` is moved into the
-/// location pointed to by `dst`.
-///
-/// # Notes
-///
-/// Rust does not currently have a rigorously and formally defined memory model,
-/// so the precise semantics of what "volatile" means here is subject to change
-/// over time. That being said, the semantics will almost always end up pretty
-/// similar to [C11's definition of volatile][c11].
-///
-/// The compiler shouldn't change the relative order or number of volatile
-/// memory operations. However, volatile memory operations on zero-sized types
-/// (e.g., if a zero-sized type is passed to `write_volatile`) are noops
-/// and may be ignored.
-///
-/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
+/// Performs a volatile write of a memory location with the given value without reading or dropping
+/// the old value.
+///
+/// Volatile operations are intended to act on I/O memory. As such, they are considered externally
+/// observable events (just like syscalls), and are guaranteed to not be elided or reordered by the
+/// compiler across other externally observable events. With this in mind, there are two cases of
+/// usage that need to be distinguished:
+///
+/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like
+///   [`write`][write()], except for the additional guarantee that it won't be elided or reordered
+///   (see above). This implies that the operation will actually access memory and not e.g. be
+///   lowered to a register access. Other than that, all the usual rules for memory accesses apply
+///   (including provenance). In particular, just like in C, whether an operation is volatile has no
+///   bearing whatsoever on questions involving concurrent access from multiple threads. Volatile
+///   accesses behave exactly like non-atomic accesses in that regard.
+///
+/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
+///   allocation. In this use-case, the pointer does *not* have to be [valid] for writes. This is
+///   typically used for CPU and peripheral registers that must be accessed via an I/O memory
+///   mapping, most commonly at fixed addresses reserved by the hardware. These often have special
+///   semantics associated to their manipulation, and cannot be used as general purpose memory.
+///   Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
+///   of such a write are well-defined by the target hardware. The provenance of the pointer is
+///   irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
+///   can cause side-effects, but those must not affect Rust-allocated memory in any way. This
+///   access is still not considered [atomic], and as such it cannot be used for inter-thread
+///   synchronization.
+///
+/// Note that volatile memory operations on zero-sized types (e.g., if a zero-sized type is passed
+/// to `write_volatile`) are noops and may be ignored.
+///
+/// `write_volatile` does not drop the contents of `dst`. This is safe, but it could leak
+/// allocations or resources, so care should be taken not to overwrite an object that should be
+/// dropped when operating on Rust memory. Additionally, it does not drop `src`. Semantically, `src`
+/// is moved into the location pointed to by `dst`.
+///
+/// [allocation]: crate::ptr#allocated-object
+/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
 ///
 /// # Safety
 ///
 /// Behavior is undefined if any of the following conditions are violated:
 ///
-/// * `dst` must be [valid] for writes.
+/// * `dst` must be either [valid] for writes, or it must point to memory outside of all Rust
+///   allocations and writing to that memory must:
+///   - not trap, and
+///   - not cause any memory inside a Rust allocation to be modified.
 ///
 /// * `dst` must be properly aligned.
 ///
@@ -2141,12 +2166,6 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
 ///
 /// [valid]: self#safety
 ///
-/// Just like in C, whether an operation is volatile has no bearing whatsoever
-/// on questions involving concurrent access from multiple threads. Volatile
-/// accesses behave exactly like non-atomic accesses in that regard. In particular,
-/// a race between a `write_volatile` and any other operation (reading or writing)
-/// on the same location is undefined behavior.
-///
 /// # Examples
 ///
 /// Basic usage:
@@ -2170,12 +2189,11 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
     unsafe {
         ub_checks::assert_unsafe_precondition!(
             check_language_ub,
-            "ptr::write_volatile requires that the pointer argument is aligned and non-null",
+            "ptr::write_volatile requires that the pointer argument is aligned",
             (
                 addr: *mut () = dst as *mut (),
                 align: usize = align_of::<T>(),
-                is_zst: bool = T::IS_ZST,
-            ) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
+            ) => ub_checks::maybe_is_aligned(addr, align)
         );
         intrinsics::volatile_store(dst, src);
     }
diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs
index a7caaeb95cd..b809294cfce 100644
--- a/library/core/src/ub_checks.rs
+++ b/library/core/src/ub_checks.rs
@@ -121,12 +121,24 @@ pub(crate) const fn maybe_is_aligned_and_not_null(
     is_zst: bool,
 ) -> bool {
     // This is just for safety checks so we can const_eval_select.
+    maybe_is_aligned(ptr, align) && (is_zst || !ptr.is_null())
+}
+
+/// Checks whether `ptr` is properly aligned with respect to the given alignment.
+///
+/// In `const` this is approximate and can fail spuriously. It is primarily intended
+/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
+/// check is anyway not executed in `const`.
+#[inline]
+#[rustc_allow_const_fn_unstable(const_eval_select)]
+pub(crate) const fn maybe_is_aligned(ptr: *const (), align: usize) -> bool {
+    // This is just for safety checks so we can const_eval_select.
     const_eval_select!(
-        @capture { ptr: *const (), align: usize, is_zst: bool } -> bool:
+        @capture { ptr: *const (), align: usize } -> bool:
         if const {
-            is_zst || !ptr.is_null()
+            true
         } else {
-            ptr.is_aligned_to(align) && (is_zst || !ptr.is_null())
+            ptr.is_aligned_to(align)
         }
     )
 }
diff --git a/tests/ui/lint/invalid_null_args.rs b/tests/ui/lint/invalid_null_args.rs
index f40f06a0d36..ee29d622ad7 100644
--- a/tests/ui/lint/invalid_null_args.rs
+++ b/tests/ui/lint/invalid_null_args.rs
@@ -58,10 +58,9 @@ unsafe fn null_ptr() {
     let _a: A = ptr::read_unaligned(ptr::null_mut());
     //~^ ERROR calling this function with a null pointer is undefined behavior
 
+    // These two should *not* fire the lint.
     let _a: A = ptr::read_volatile(ptr::null());
-    //~^ ERROR calling this function with a null pointer is undefined behavior
     let _a: A = ptr::read_volatile(ptr::null_mut());
-    //~^ ERROR calling this function with a null pointer is undefined behavior
 
     let _a: A = ptr::replace(ptr::null_mut(), v);
     //~^ ERROR calling this function with a null pointer is undefined behavior
@@ -82,8 +81,8 @@ unsafe fn null_ptr() {
     ptr::write_unaligned(ptr::null_mut(), v);
     //~^ ERROR calling this function with a null pointer is undefined behavior
 
+    // This one should *not* fire the lint.
     ptr::write_volatile(ptr::null_mut(), v);
-    //~^ ERROR calling this function with a null pointer is undefined behavior
 
     ptr::write_bytes::<usize>(ptr::null_mut(), 42, 0);
     //~^ ERROR calling this function with a null pointer is undefined behavior
diff --git a/tests/ui/lint/invalid_null_args.stderr b/tests/ui/lint/invalid_null_args.stderr
index 11c6270cfb7..028bd7051dc 100644
--- a/tests/ui/lint/invalid_null_args.stderr
+++ b/tests/ui/lint/invalid_null_args.stderr
@@ -164,27 +164,7 @@ LL |     let _a: A = ptr::read_unaligned(ptr::null_mut());
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:61:17
-   |
-LL |     let _a: A = ptr::read_volatile(ptr::null());
-   |                 ^^^^^^^^^^^^^^^^^^^-----------^
-   |                                    |
-   |                                    null pointer originates from here
-   |
-   = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
-
-error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:63:17
-   |
-LL |     let _a: A = ptr::read_volatile(ptr::null_mut());
-   |                 ^^^^^^^^^^^^^^^^^^^---------------^
-   |                                    |
-   |                                    null pointer originates from here
-   |
-   = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
-
-error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:66:17
+  --> $DIR/invalid_null_args.rs:65:17
    |
 LL |     let _a: A = ptr::replace(ptr::null_mut(), v);
    |                 ^^^^^^^^^^^^^---------------^^^^
@@ -194,7 +174,7 @@ LL |     let _a: A = ptr::replace(ptr::null_mut(), v);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:69:5
+  --> $DIR/invalid_null_args.rs:68:5
    |
 LL |     ptr::swap::<A>(ptr::null_mut(), &mut v);
    |     ^^^^^^^^^^^^^^^---------------^^^^^^^^^
@@ -204,7 +184,7 @@ LL |     ptr::swap::<A>(ptr::null_mut(), &mut v);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:71:5
+  --> $DIR/invalid_null_args.rs:70:5
    |
 LL |     ptr::swap::<A>(&mut v, ptr::null_mut());
    |     ^^^^^^^^^^^^^^^^^^^^^^^---------------^
@@ -214,7 +194,7 @@ LL |     ptr::swap::<A>(&mut v, ptr::null_mut());
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:74:5
+  --> $DIR/invalid_null_args.rs:73:5
    |
 LL |     ptr::swap_nonoverlapping::<A>(ptr::null_mut(), &mut v, 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^^^^^^^^^
@@ -224,7 +204,7 @@ LL |     ptr::swap_nonoverlapping::<A>(ptr::null_mut(), &mut v, 0);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:76:5
+  --> $DIR/invalid_null_args.rs:75:5
    |
 LL |     ptr::swap_nonoverlapping::<A>(&mut v, ptr::null_mut(), 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^
@@ -234,7 +214,7 @@ LL |     ptr::swap_nonoverlapping::<A>(&mut v, ptr::null_mut(), 0);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:79:5
+  --> $DIR/invalid_null_args.rs:78:5
    |
 LL |     ptr::write(ptr::null_mut(), v);
    |     ^^^^^^^^^^^---------------^^^^
@@ -244,7 +224,7 @@ LL |     ptr::write(ptr::null_mut(), v);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:82:5
+  --> $DIR/invalid_null_args.rs:81:5
    |
 LL |     ptr::write_unaligned(ptr::null_mut(), v);
    |     ^^^^^^^^^^^^^^^^^^^^^---------------^^^^
@@ -254,17 +234,7 @@ LL |     ptr::write_unaligned(ptr::null_mut(), v);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:85:5
-   |
-LL |     ptr::write_volatile(ptr::null_mut(), v);
-   |     ^^^^^^^^^^^^^^^^^^^^---------------^^^^
-   |                         |
-   |                         null pointer originates from here
-   |
-   = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
-
-error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:88:5
+  --> $DIR/invalid_null_args.rs:87:5
    |
 LL |     ptr::write_bytes::<usize>(ptr::null_mut(), 42, 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^^^^^
@@ -274,7 +244,7 @@ LL |     ptr::write_bytes::<usize>(ptr::null_mut(), 42, 0);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:93:18
+  --> $DIR/invalid_null_args.rs:92:18
    |
 LL |     let _a: u8 = ptr::read(const_ptr);
    |                  ^^^^^^^^^^^^^^^^^^^^
@@ -287,7 +257,7 @@ LL |     let null_ptr = ptr::null_mut();
    |                    ^^^^^^^^^^^^^^^
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:100:5
+  --> $DIR/invalid_null_args.rs:99:5
    |
 LL |     std::slice::from_raw_parts::<()>(ptr::null(), 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^
@@ -297,7 +267,7 @@ LL |     std::slice::from_raw_parts::<()>(ptr::null(), 0);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:102:5
+  --> $DIR/invalid_null_args.rs:101:5
    |
 LL |     std::slice::from_raw_parts::<Zst>(ptr::null(), 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^
@@ -307,7 +277,7 @@ LL |     std::slice::from_raw_parts::<Zst>(ptr::null(), 0);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:104:5
+  --> $DIR/invalid_null_args.rs:103:5
    |
 LL |     std::slice::from_raw_parts_mut::<()>(ptr::null_mut(), 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^
@@ -317,7 +287,7 @@ LL |     std::slice::from_raw_parts_mut::<()>(ptr::null_mut(), 0);
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
 error: calling this function with a null pointer is undefined behavior, even if the result of the function is unused
-  --> $DIR/invalid_null_args.rs:106:5
+  --> $DIR/invalid_null_args.rs:105:5
    |
 LL |     std::slice::from_raw_parts_mut::<Zst>(ptr::null_mut(), 0);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^
@@ -326,5 +296,5 @@ LL |     std::slice::from_raw_parts_mut::<Zst>(ptr::null_mut(), 0);
    |
    = help: for more information, visit <https://doc.rust-lang.org/std/ptr/index.html> and <https://doc.rust-lang.org/reference/behavior-considered-undefined.html>
 
-error: aborting due to 31 previous errors
+error: aborting due to 28 previous errors
 
diff --git a/tests/ui/precondition-checks/read_volatile.rs b/tests/ui/precondition-checks/read_volatile.rs
index fa1c2c827d7..33350dfbc4f 100644
--- a/tests/ui/precondition-checks/read_volatile.rs
+++ b/tests/ui/precondition-checks/read_volatile.rs
@@ -1,9 +1,7 @@
 //@ run-crash
 //@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes
 //@ error-pattern: unsafe precondition(s) violated: ptr::read_volatile requires
-//@ revisions: null misaligned
-
-#![allow(invalid_null_arguments)]
+//@ revisions: misaligned
 
 use std::ptr;
 
@@ -11,8 +9,6 @@ fn main() {
     let src = [0u16; 2];
     let src = src.as_ptr();
     unsafe {
-        #[cfg(null)]
-        ptr::read_volatile(ptr::null::<u8>());
         #[cfg(misaligned)]
         ptr::read_volatile(src.byte_add(1));
     }
diff --git a/tests/ui/precondition-checks/write_volatile.rs b/tests/ui/precondition-checks/write_volatile.rs
index 25107871c39..d6ad6320e41 100644
--- a/tests/ui/precondition-checks/write_volatile.rs
+++ b/tests/ui/precondition-checks/write_volatile.rs
@@ -1,9 +1,7 @@
 //@ run-crash
 //@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes
 //@ error-pattern: unsafe precondition(s) violated: ptr::write_volatile requires
-//@ revisions: null misaligned
-
-#![allow(invalid_null_arguments)]
+//@ revisions: misaligned
 
 use std::ptr;
 
@@ -11,8 +9,6 @@ fn main() {
     let mut dst = [0u16; 2];
     let mut dst = dst.as_mut_ptr();
     unsafe {
-        #[cfg(null)]
-        ptr::write_volatile(ptr::null_mut::<u8>(), 1u8);
         #[cfg(misaligned)]
         ptr::write_volatile(dst.byte_add(1), 1u16);
     }