Skip to content

Commit b3ebf67

Browse files
committed
transpile: add cleanup attribute test
1 parent 51071f9 commit b3ebf67

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 function-scope cleanups complete on return; the outer `run_*`
20+
* then copies the recorded trace. */
21+
22+
static void do_single(void) {
23+
int x __attribute__((cleanup(record))) = 5;
24+
(void)x;
25+
}
26+
27+
void run_single(int *out, int *n) {
28+
reset();
29+
do_single();
30+
copy_trace(out, n);
31+
}
32+
33+
static void do_multiple(void) {
34+
int a __attribute__((cleanup(record))) = 1;
35+
int b __attribute__((cleanup(record))) = 2;
36+
int c __attribute__((cleanup(record))) = 3;
37+
(void)a; (void)b; (void)c;
38+
}
39+
40+
void run_multiple(int *out, int *n) {
41+
reset();
42+
do_multiple();
43+
copy_trace(out, n);
44+
}
45+
46+
static void do_early_return(void) {
47+
int x __attribute__((cleanup(record))) = 7;
48+
(void)x;
49+
return;
50+
}
51+
52+
void run_early_return(int *out, int *n) {
53+
reset();
54+
do_early_return();
55+
copy_trace(out, n);
56+
}
57+
58+
static void do_nested(void) {
59+
int outer __attribute__((cleanup(record))) = 10;
60+
{
61+
int inner __attribute__((cleanup(record))) = 20;
62+
(void)inner;
63+
}
64+
int after = 30;
65+
record(&after);
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, 30, 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)