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

Modification to volatile variable between setjmp/longjmp not retained in translated rust code #1167

Open
Yeaseen opened this issue Nov 24, 2024 · 0 comments

Comments

@Yeaseen
Copy link

Yeaseen commented Nov 24, 2024

Description

When translating C code that uses setjmp/longjmp along with volatile variables, c2rust produces Rust code that does not preserve the expected behavior seen in the original C code, particularly with regard to variable state post-longjmp.

Source C code

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf buf;

void test_function() {
    static int call_count = 0;
    call_count++;
    printf("test_function called %d time(s)\n", call_count);
    if (call_count == 2) {
        longjmp(buf, 1);
    }
}
int main(void) {
    volatile int loop_var = 0;
    if (setjmp(buf) == 0) {
        for (loop_var = 0; loop_var < 5; loop_var++) {
            printf("loop_var before function call: %d\n", loop_var);
            test_function();
            printf("loop_var after function call: %d\n", loop_var);
        }
    } else {
        printf("Returned via longjmp with loop_var: %d\n", loop_var);
    }
    return 0;
}

Output by gcc and clang

loop_var before function call: 0
test_function called 1 time(s)
loop_var after function call: 0
loop_var before function call: 1
test_function called 2 time(s)
Returned via longjmp with loop_var: 1

The C program outputs the value of loop_var as 1 after the longjmp is triggered because the volatile keyword ensures the variable is updated directly in memory, and longjmp restores this correctly.

godbolt view

Translated Rust code

Click here to view the 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 printf(_: *const libc::c_char, _: ...) -> libc::c_int;
    fn _setjmp(_: *mut __jmp_buf_tag) -> libc::c_int;
    fn longjmp(_: *mut __jmp_buf_tag, _: libc::c_int) -> !;
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct __sigset_t {
    pub __val: [libc::c_ulong; 16],
}
pub type __jmp_buf = [libc::c_long; 8];
#[derive(Copy, Clone)]
#[repr(C)]
pub struct __jmp_buf_tag {
    pub __jmpbuf: __jmp_buf,
    pub __mask_was_saved: libc::c_int,
    pub __saved_mask: __sigset_t,
}
pub type jmp_buf = [__jmp_buf_tag; 1];
#[no_mangle]
pub static mut buf: jmp_buf = [__jmp_buf_tag {
    __jmpbuf: [0; 8],
    __mask_was_saved: 0,
    __saved_mask: __sigset_t { __val: [0; 16] },
}; 1];
#[no_mangle]
pub unsafe extern "C" fn test_function() {
    static mut call_count: libc::c_int = 0 as libc::c_int;
    call_count += 1;
    call_count;
    printf(
        b"test_function called %d time(s)\n\0" as *const u8 as *const libc::c_char,
        call_count,
    );
    if call_count == 2 as libc::c_int {
        longjmp(buf.as_mut_ptr(), 1 as libc::c_int);
    }
}
unsafe fn main_0() -> libc::c_int {
    let mut loop_var: libc::c_int = 0 as libc::c_int;
    if _setjmp(buf.as_mut_ptr()) == 0 as libc::c_int {
        ::core::ptr::write_volatile(&mut loop_var as *mut libc::c_int, 0 as libc::c_int);
        while loop_var < 5 as libc::c_int {
            printf(
                b"loop_var before function call: %d\n\0" as *const u8
                    as *const libc::c_char,
                loop_var,
            );
            test_function();
            printf(
                b"loop_var after function call: %d\n\0" as *const u8
                    as *const libc::c_char,
                loop_var,
            );
            ::core::ptr::write_volatile(
                &mut loop_var as *mut libc::c_int,
                ::core::ptr::read_volatile::<
                    libc::c_int,
                >(&loop_var as *const libc::c_int) + 1,
            );
            ::core::ptr::read_volatile::<libc::c_int>(&loop_var as *const libc::c_int);
        }
    } else {
        printf(
            b"Returned via longjmp with loop_var: %d\n\0" as *const u8
                as *const libc::c_char,
            loop_var,
        );
    }
    return 0 as libc::c_int;
}
pub fn main() {
    unsafe { ::std::process::exit(main_0() as i32) }
}

Translated Rust code's output

loop_var before function call: 0
test_function called 1 time(s)
loop_var after function call: 0
loop_var before function call: 1
test_function called 2 time(s)
Returned via longjmp with loop_var: 0

Observation

The translated Rust code fails to preserve the updated value of loop_var post-longjmp. Instead, it incorrectly retains the initial value (i.e., 0 instead of 1), indicating a potential issue with how volatile access or setjmp/longjmp semantics are handled in the translation. This discrepancy may stem from how c2rust translates the volatile qualifier and the semantics of setjmp/longjmp.

@Yeaseen Yeaseen changed the title Inconsistent Behavior with Translated setjmp/longjmp and volatile Usage Modification to volatile variable between setjmp/longjmp not retained in translated rust code Nov 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant