Warning about dereferencing a null pointer

The warning:

warning: dereferencing a null pointer
     --> src/generated/vulkan_bundle.rs:62648:14
      |
62648 |             &(*(::std::ptr::null::<VkPhysicalDeviceRayQueryFeaturesKHR>())).rayQuery as *const _
      |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
    assert_eq!(
        unsafe {
            &(*(::std::ptr::null::<VkPhysicalDeviceRayQueryFeaturesKHR>())).rayQuery as *const _
                as usize
        },
        16usize,
        concat!(
            "Offset of field: ",
            stringify!(VkPhysicalDeviceRayQueryFeaturesKHR),
            "::",
            stringify!(rayQuery)
        )
    );

Test results:

test generated::vulkan_bundle::bindgen_test_layout_VkPhysicalDeviceTimelineSemaphoreFeatures ... ok
test generated::vulkan_bundle::bindgen_test_layout_VkPhysicalDeviceVulkan12Features ... ok
test generated::vulkan_bundle::bindgen_test_layout_VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR ... ok

test result: ok. 560 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.74s

I got hundreds of warnings, in the tests generated by bindgen 0.59.1, that never appeared before, I recently updated to rust 1.55. The warning seems to make sense to me, a null pointer is created and then a member is accessed through it. The strange thing is that it runs normally. What could be happening?

1 Like

Undefined Behavior is allowed to behave as expected

2 Likes

Yes, but a null pointer is created and used, shouldn't that cause the program crash?

The compiler is allowed to optimize the whole operation down to a constant, or in fact to anything else (that's what undefined behavior means).

I'm curious, is the original C code also dereferencing NULL or is this bindgen generating a bad dereference?

2 Likes

I got it. But wouldn't it make more sense to create an empty object with std::mem:zeroed()?
Is a layout test automatically generated by bindgen.

What it acutally does is &(TypedNull.field) as *FieldPtr as usize, so it doesn't really dereference the pointer, it just calculates the offset to field in a roundabout way (if null was a proper *TypedNull, what would be the adress of it's field).

Not sure if this is a good way of getting the offset or not, but I don't think it actually executes anything undefined, it just looks at it rather closely ...

1 Like

Ugh, this seems to be a known issue with layout tests.

2 Likes

&((*null()).field) is definitely UB since it creates (temporarily) a reference that's not valid.

3 Likes

It's definitely a weird way to get the offset. The compiler and I were confused.

Creating an invalid pointer is not UB, only dereferencing it is. And yes, this code looks dangerously close to UB, but it doesn't actually dereference the bad pointer.

1 Like

Creating an invalid reference, even without using it, is UB. Try running Miri on this example.

The right way to do this in Rust is the recently stabilized addr_of macro, which avoids dereferencing.

9 Likes

The addr_of documentation specifically calls out

Note, however, that the expr in addr_of!(expr) is still subject to all the usual rules. In particular, addr_of!(*ptr::null()) is Undefined Behavior because it dereferences a null pointer.

So more is needed to fix bindgen's mess here. I think you could do

let x = MaybeUninit::uninit();
unsafe { addr_of!((*x.as_ptr()).field) } as usize - x.as_ptr() as usize

Miri is okay with this version, at least.

3 Likes

MaybeUninit is certainly the mechanism used by the offset_of! macro, which there's been talk of upstreaming. Don't know if it would be worth pulling in for the generated code.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.