Skip to content

Commit ff98f60

Browse files
committed
transpile: add cleanup attribute test
1 parent 9d2c082 commit ff98f60

4 files changed

Lines changed: 183 additions & 0 deletions

File tree

tests/unit/cleanup_attr/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "cleanup-attr-tests"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]

tests/unit/cleanup_attr/build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use std::env;
2+
3+
fn main() {
4+
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
5+
6+
println!("cargo:rustc-link-search=native={}", manifest_dir);
7+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
static int trace_buf[16];
2+
static int trace_n;
3+
4+
static void reset(void) {
5+
trace_n = 0;
6+
}
7+
8+
static void copy_trace(int *out, int *n) {
9+
int i;
10+
for (i = 0; i < trace_n; i++) out[i] = trace_buf[i];
11+
*n = trace_n;
12+
}
13+
14+
static void record(int *p) {
15+
if (trace_n < 16) trace_buf[trace_n++] = *p;
16+
}
17+
18+
/* Each `do_*` helper runs the cleanup-bearing code in its own function so
19+
* that all cleanups complete on return; the outer `run_*` then copies the
20+
* recorded trace. This matters because c2rust flattens nested C blocks
21+
* into the enclosing function scope, so `cleanup` doesn't fire at the
22+
* end of a `{ ... }` block, only at function return. */
23+
24+
static void do_single(void) {
25+
int x __attribute__((cleanup(record))) = 5;
26+
(void)x;
27+
}
28+
29+
void run_single(int *out, int *n) {
30+
reset();
31+
do_single();
32+
copy_trace(out, n);
33+
}
34+
35+
static void do_multiple(void) {
36+
int a __attribute__((cleanup(record))) = 1;
37+
int b __attribute__((cleanup(record))) = 2;
38+
int c __attribute__((cleanup(record))) = 3;
39+
(void)a; (void)b; (void)c;
40+
}
41+
42+
void run_multiple(int *out, int *n) {
43+
reset();
44+
do_multiple();
45+
copy_trace(out, n);
46+
}
47+
48+
static void do_early_return(void) {
49+
int x __attribute__((cleanup(record))) = 7;
50+
(void)x;
51+
return;
52+
}
53+
54+
void run_early_return(int *out, int *n) {
55+
reset();
56+
do_early_return();
57+
copy_trace(out, n);
58+
}
59+
60+
static void do_nested(void) {
61+
int outer __attribute__((cleanup(record))) = 10;
62+
{
63+
int inner __attribute__((cleanup(record))) = 20;
64+
(void)inner;
65+
}
66+
(void)outer;
67+
}
68+
69+
void run_nested(int *out, int *n) {
70+
reset();
71+
do_nested();
72+
copy_trace(out, n);
73+
}
74+
75+
/* Back-edge via goto forces c2rust to hoist locals. The cleanup variable
76+
* is naturally reached, but its declaration sits in a hoisted block, so
77+
* this verifies the guard runs on the assignment site rather than on the
78+
* bare zero-init at function top. */
79+
static void do_goto(void) {
80+
int counter __attribute__((cleanup(record))) = 0;
81+
again:
82+
counter++;
83+
if (counter < 3) goto again;
84+
}
85+
86+
void run_goto(int *out, int *n) {
87+
reset();
88+
do_goto();
89+
copy_trace(out, n);
90+
}
91+
92+
typedef struct {
93+
int v;
94+
} widget_t;
95+
96+
static void widget_destroy(widget_t *w) {
97+
record(&w->v);
98+
}
99+
100+
static void do_typedef(void) {
101+
widget_t w __attribute__((cleanup(widget_destroy))) = { 42 };
102+
(void)w;
103+
}
104+
105+
void run_typedef(int *out, int *n) {
106+
reset();
107+
do_typedef();
108+
copy_trace(out, n);
109+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use crate::cleanup::{
2+
rust_run_early_return, rust_run_goto, rust_run_multiple, rust_run_nested, rust_run_single,
3+
rust_run_typedef,
4+
};
5+
use std::ffi::c_int;
6+
7+
#[link(name = "test")]
8+
unsafe extern "C" {
9+
fn run_single(out: *mut c_int, n: *mut c_int);
10+
fn run_multiple(out: *mut c_int, n: *mut c_int);
11+
fn run_early_return(out: *mut c_int, n: *mut c_int);
12+
fn run_nested(out: *mut c_int, n: *mut c_int);
13+
fn run_goto(out: *mut c_int, n: *mut c_int);
14+
fn run_typedef(out: *mut c_int, n: *mut c_int);
15+
}
16+
17+
type EntryFn = unsafe extern "C" fn(out: *mut c_int, n: *mut c_int);
18+
19+
fn collect(entry: EntryFn) -> Vec<c_int> {
20+
let mut buf = [0i32; 16];
21+
let mut n: c_int = 0;
22+
unsafe { entry(buf.as_mut_ptr(), &mut n) };
23+
buf[..n as usize].to_vec()
24+
}
25+
26+
fn check(c_entry: EntryFn, rust_entry: EntryFn, expected: &[c_int]) {
27+
let c_trace = collect(c_entry);
28+
let rust_trace = collect(rust_entry);
29+
assert_eq!(c_trace, expected, "C side diverged from expected");
30+
assert_eq!(rust_trace, expected, "Rust side diverged from expected");
31+
}
32+
33+
#[test]
34+
pub fn test_single() {
35+
check(run_single, rust_run_single, &[5]);
36+
}
37+
38+
#[test]
39+
pub fn test_reverse_declaration_order() {
40+
check(run_multiple, rust_run_multiple, &[3, 2, 1]);
41+
}
42+
43+
#[test]
44+
pub fn test_fires_through_early_return() {
45+
check(run_early_return, rust_run_early_return, &[7]);
46+
}
47+
48+
#[test]
49+
pub fn test_nested_block() {
50+
check(run_nested, rust_run_nested, &[20, 10]);
51+
}
52+
53+
#[test]
54+
pub fn test_goto_with_hoisted_local() {
55+
check(run_goto, rust_run_goto, &[3]);
56+
}
57+
58+
#[test]
59+
pub fn test_typedef_signature() {
60+
check(run_typedef, rust_run_typedef, &[42]);
61+
}

0 commit comments

Comments
 (0)