Skip to content

transpile: lifetime of array to ptr decay not correctly translated and extended #1217

@Yeaseen

Description

@Yeaseen

This bug arises when using a compound literal to initialize a struct containing a pthread_rwlock_t, and passing a pointer to that struct to a thread. While this is safe and valid in C, the Rust code generated by C2Rust mishandles the struct's memory lifetime or layout, resulting in corrupted field values at runtime.

This issue is distinct from [#1216], which involves PTHREAD_MUTEX_INITIALIZER and leads to a runtime crash. In contrast, this bug causes silent data corruption without triggering a panic or assertion failure.

Source C code:

#include <pthread.h>
#include <stdio.h>

typedef struct {
    pthread_rwlock_t lock;
    int val;
} S;

void *reader(void *arg) {
    S *s = arg;
    pthread_rwlock_rdlock(&s->lock);
    printf("val: %d\n", s->val);
    pthread_rwlock_unlock(&s->lock);
    return NULL;
}

int main() {
    pthread_t t;
    S *s = (S[]){ { PTHREAD_RWLOCK_INITIALIZER, 42 } };
    pthread_create(&t, NULL, reader, s);
    pthread_join(t, NULL);
    return 0;
}

C Output:

val: 42
Click to view translated Rust code
#![allow(
    dead_code,
    mutable_transmutes,
    non_camel_case_types,
    non_snake_case,
    non_upper_case_globals,
    unused_assignments,
    unused_mut
)]
use ::transpiled_code::*;
extern "C" {
    fn pthread_create(
        __newthread: *mut pthread_t,
        __attr: *const pthread_attr_t,
        __start_routine: Option::<
            unsafe extern "C" fn(*mut libc::c_void) -> *mut libc::c_void,
        >,
        __arg: *mut libc::c_void,
    ) -> libc::c_int;
    fn pthread_join(
        __th: pthread_t,
        __thread_return: *mut *mut libc::c_void,
    ) -> libc::c_int;
    fn pthread_rwlock_rdlock(__rwlock: *mut pthread_rwlock_t) -> libc::c_int;
    fn pthread_rwlock_unlock(__rwlock: *mut pthread_rwlock_t) -> libc::c_int;
    fn printf(_: *const libc::c_char, _: ...) -> libc::c_int;
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct __pthread_rwlock_arch_t {
    pub __readers: libc::c_uint,
    pub __writers: libc::c_uint,
    pub __wrphase_futex: libc::c_uint,
    pub __writers_futex: libc::c_uint,
    pub __pad3: libc::c_uint,
    pub __pad4: libc::c_uint,
    pub __cur_writer: libc::c_int,
    pub __shared: libc::c_int,
    pub __rwelision: libc::c_schar,
    pub __pad1: [libc::c_uchar; 7],
    pub __pad2: libc::c_ulong,
    pub __flags: libc::c_uint,
}
pub type pthread_t = libc::c_ulong;
#[derive(Copy, Clone)]
#[repr(C)]
pub union pthread_attr_t {
    pub __size: [libc::c_char; 56],
    pub __align: libc::c_long,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union pthread_rwlock_t {
    pub __data: __pthread_rwlock_arch_t,
    pub __size: [libc::c_char; 56],
    pub __align: libc::c_long,
}
pub type C2RustUnnamed = libc::c_uint;
pub const PTHREAD_RWLOCK_DEFAULT_NP: C2RustUnnamed = 0;
pub const PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: C2RustUnnamed = 2;
pub const PTHREAD_RWLOCK_PREFER_WRITER_NP: C2RustUnnamed = 1;
pub const PTHREAD_RWLOCK_PREFER_READER_NP: C2RustUnnamed = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct S {
    pub lock: pthread_rwlock_t,
    pub val: libc::c_int,
}
#[no_mangle]
pub unsafe extern "C" fn reader(mut arg: *mut libc::c_void) -> *mut libc::c_void {
    let mut s: *mut S = arg as *mut S;
    pthread_rwlock_rdlock(&mut (*s).lock);
    printf(b"val: %d\n\0" as *const u8 as *const libc::c_char, (*s).val);
    pthread_rwlock_unlock(&mut (*s).lock);
    return 0 as *mut libc::c_void;
}
unsafe fn main_0() -> libc::c_int {
    let mut t: pthread_t = 0;
    let mut s: *mut S = [
        {
            let mut init = S {
                lock: pthread_rwlock_t {
                    __data: {
                        let mut init = __pthread_rwlock_arch_t {
                            __readers: 0 as libc::c_int as libc::c_uint,
                            __writers: 0 as libc::c_int as libc::c_uint,
                            __wrphase_futex: 0 as libc::c_int as libc::c_uint,
                            __writers_futex: 0 as libc::c_int as libc::c_uint,
                            __pad3: 0 as libc::c_int as libc::c_uint,
                            __pad4: 0 as libc::c_int as libc::c_uint,
                            __cur_writer: 0 as libc::c_int,
                            __shared: 0 as libc::c_int,
                            __rwelision: 0 as libc::c_int as libc::c_schar,
                            __pad1: [
                                0 as libc::c_int as libc::c_uchar,
                                0 as libc::c_int as libc::c_uchar,
                                0 as libc::c_int as libc::c_uchar,
                                0 as libc::c_int as libc::c_uchar,
                                0 as libc::c_int as libc::c_uchar,
                                0 as libc::c_int as libc::c_uchar,
                                0 as libc::c_int as libc::c_uchar,
                            ],
                            __pad2: 0 as libc::c_int as libc::c_ulong,
                            __flags: PTHREAD_RWLOCK_DEFAULT_NP as libc::c_int
                                as libc::c_uint,
                        };
                        init
                    },
                },
                val: 42 as libc::c_int,
            };
            init
        },
    ]
        .as_mut_ptr();
    pthread_create(
        &mut t,
        0 as *const pthread_attr_t,
        Some(reader as unsafe extern "C" fn(*mut libc::c_void) -> *mut libc::c_void),
        s as *mut libc::c_void,
    );
    pthread_join(t, 0 as *mut *mut libc::c_void);
    return 0 as libc::c_int;
}
pub fn main() {
    unsafe { ::std::process::exit(main_0() as i32) }
}

Translated Rust Output:

Rust code prints incorrect value (e.g., val: 0) or worse — leads to undefined behavior.

val: 0

Root Cause

C2Rust does not promote the temporary to a valid heap-allocated or static location. As a result, the memory becomes invalid by the time the thread accesses it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions