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

static mut translation adds UB, but could be safe #782

Open
Dante-Broggi opened this issue Jan 16, 2023 · 3 comments
Open

static mut translation adds UB, but could be safe #782

Dante-Broggi opened this issue Jan 16, 2023 · 3 comments
Labels
bug Something isn't working

Comments

@Dante-Broggi
Copy link

Thread locals are currently translated as static mut which is unsafe, but more importantly are accessed by &mut which adds UB compared to C.

As a minimal example this:

static __thread int x = 0;
void bar(int *y) {
    int *z = &x;
    *z = *y;
}
void foo() { bar(&x); }

Currently translates on the website (https://c2rust.com) as:

#[thread_local]
static mut x: libc::c_int = 0 as libc::c_int;
#[no_mangle]
pub unsafe extern "C" fn bar(mut y: *mut libc::c_int) {
    let mut z: *mut libc::c_int = &mut x;
    *z = *y;
}
#[no_mangle]
pub unsafe extern "C" fn foo() {
    bar(&mut x);
}

Which creates 2 &mut x references and uses the first after the construction of the second, leading to UB absent in the original C code.
But it could use translate as:

use core::cell::Cell;
#[thread_local]
static x: Cell<libc::c_int> = Cell::new(0 as libc::c_int);
#[no_mangle]
pub unsafe extern "C" fn bar(mut y: *mut libc::c_int) {
    let mut z: *mut libc::c_int = x.as_ptr();
    *z = *y;
}
#[no_mangle]
pub unsafe extern "C" fn foo() {
    bar(x.as_ptr());
}

This translation would prevent the UB from creating multiple &mut to the same static, and moreover makes the definition of x itself completely safe. It would even work with pointer typed static __thread variables because unlike normal statics, #[thread_local] static does not need to be Sync.

@kkysen
Copy link
Contributor

kkysen commented Jan 30, 2023

What we should be doing, more generally as well, is always using addr_of_mut! instead of &mut so that we never create temporary references where we don't need to. Replacing the &muts here with addr_of_mut!s gets rid of the UB, as per miri. See #301 for more on this.

That said, a translation using Cell seems quite smart here, as then accessing the static is fully safe, as using .as_ptr() only doesn't incur the T: Copy restriction .get() adds. Most of this is due to {Cell,UnsafeCell}::as_ptr being basically the same as addr_of_mut!. We can see if this approach works.

@kkysen
Copy link
Contributor

kkysen commented Jan 30, 2023

I realized that the __thread and #[thread_local] don't actually play a role here. The same UB can be reproduced without them:

static int x = 0;
void bar(int *y) {
    int *z = &x;
    *z = *y;
}
void foo() { bar(&x); }

playground link

static mut x: libc::c_int = 0 as libc::c_int;
#[no_mangle]
pub unsafe extern "C" fn bar(mut y: *mut libc::c_int) {
    let mut z: *mut libc::c_int = &mut x;
    *z = *y;
}
#[no_mangle]
pub unsafe extern "C" fn foo() {
    bar(&mut x);
}

@kkysen kkysen changed the title Thread local translation adds UB, but could be safe static mut translation adds UB, but could be safe Jan 30, 2023
@kkysen kkysen added the bug Something isn't working label Jan 30, 2023
@Dante-Broggi
Copy link
Author

The UB is indeed independent of __thread but the fix of using Cell requires it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants