Skip to content
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

False positive const-UB check on NonNull pointer #133523

Closed
JakobDegen opened this issue Nov 27, 2024 · 4 comments · Fixed by #133700
Closed

False positive const-UB check on NonNull pointer #133523

JakobDegen opened this issue Nov 27, 2024 · 4 comments · Fixed by #133700
Labels
A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@JakobDegen
Copy link
Contributor

JakobDegen commented Nov 27, 2024

I tried this code:

use std::ptr::NonNull;

const X: NonNull<()> = {
    let r: &'static u16 = &0;
    
    let p = unsafe { std::mem::transmute::<_, *mut ()>(r) };
    let p = p.wrapping_byte_add(3);
    unsafe { NonNull::new_unchecked(p) }
};

I expected to see this happen: Compiles. This code cannot be UB, as the alignment on the static guarantees that the produced pointer has odd address and therefore is non-null.

Instead, this happened:

error[E0080]: it is undefined behavior to use this value
 --> src/lib.rs:3:1
  |
3 | const X: NonNull<()> = {
  | ^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a potentially null pointer, but expected something that cannot possibly fail to be greater or equal to 1
  |
  = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
  = note: the raw bytes of the constant (size: 8, align: 8) {
              ╾───alloc3+0x3<imm>───╼                         │ ╾──────╼
          }

For more information about this error, try `rustc --explain E0080`.

The check seems to know that it might sometimes be wrong, but this isn't a case where the rules around UB are unclear - the rules around what pointers you can put in a NonNull are very clear.

Meta

All versions of Rust since 1.61 where the necessary operations were stabilized in const

@JakobDegen JakobDegen added A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) C-bug Category: This is a bug. labels Nov 27, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Nov 27, 2024
@JakobDegen JakobDegen added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Nov 27, 2024
@scottmcm
Copy link
Member

What's the situation in which you want to do this?

The plan is for NonNull::new to compile-fail on this soon too, as "well don't do that", so if this is something you actually need for something it'd be good to hear why.

Or is it just a "please change the error message to say something else"?

@JakobDegen
Copy link
Contributor Author

JakobDegen commented Nov 27, 2024

@scottmcm this comes up from doing a sequence of two memory optimizations. The first one is this type. For our purposes we can treat it as a Box<[u64]> but with the following representation: The value itself is a pointer to the heap allocated slice data, and the length is stored before that data (so at pointer - 8). For the size zero case, we can then point one-past-the-end of a statically allocate u64. The benefit of this over Box<[u64]> is that it saves 8 bytes in the length zero case.

The issue comes up once I wanted to use the low bits of that pointer to additionally bitpack in some more data. So, imagine that I wanted to store a (ThinBoxSlice, bool), but represented as thin_box_slice_ptr | bool_val. A ([], True) of that type ought to be statically allocatable, but runs into the issue described here, since the bit-packing ends up adding one to what was already a one-past-the-end pointer.

That being said, now that I'm thinking about this though, I don't think that this should be hard to fix - it should be completely fine to replace the if offset > allocation_size with a if offset > allocation_size && offset % allocation_align == 0 - that results in what I think is actually a correct check.

@jieyouxu jieyouxu added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Nov 27, 2024
@RalfJung
Copy link
Member

RalfJung commented Dec 1, 2024

This code cannot be UB, as the alignment on the static guarantees that the produced pointer has odd address and therefore is non-null.

Yeah we could make our null-check logic more complicated to account for this. That would immediately become a stable guarantee though, now that is_null can be called on stable.

@RalfJung
Copy link
Member

RalfJung commented Dec 1, 2024

#133700 should fix this :)

@bors bors closed this as completed in b78edd7 Jan 18, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Jan 18, 2025
Rollup merge of rust-lang#133700 - RalfJung:const-non-null, r=lcnr

const-eval: detect more pointers as definitely not-null

This fixes rust-lang#133523 by making the `scalar_may_be_null` check smarter: for instance, an odd offset in any 2-aligned allocation can never be null, even if it is out-of-bounds.

More generally, if an allocation with unknown base address B is aligned to alignment N, and a pointer is at offset X inside that allocation, then we know that `(B + X) mod N = B mod N + X mod N = X mod N`. Since `0 mod N` is definitely 0, if we learn that `X mod N` is *not* 0 we can deduce that `B + X` is not 0.

This is immediately visible on stable, via `ptr.is_null()` (and, more subtly, by not raising a UB error when such a pointer is used somewhere that a non-null pointer is required). Therefore nominating for `@rust-lang/lang.`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants