Skip to content
File

Blob: src/rust/cxx-integration-test/lib.rs

rust218 lines
1//! Non-production crate to help test various aspects of rust/c++ integration.
2 
3use std::io::Error;
4use std::pin::Pin;
5use std::time::Duration;
6 
7use tracing::debug;
8use tracing::error;
9use tracing::info;
10use tracing::trace;
11use tracing::warn;
12 
13type Result<T> = std::io::Result<T>;
14 
15#[cxx::bridge(namespace = "workerd::rust::test")]
16mod ffi {
17 unsafe extern "C++" {
18 // To use a C++ callback first define it as an opaque to Rust type.
19 type TestCallback;
20 
21 // Then define a call function with a correct signature.
22 // cxx can't call operator() but can call an ordinary function.
23 // Use C++ preprocessor to alias operatorCALL to operator().
24 #[cxx_name = "operatorCALL"]
25 fn call(self: Pin<&mut TestCallback>, a: usize, b: usize) -> usize;
26 
27 // Include the header with the actual callback type definition.
28 // This will be included into cxx generated files.
29 include!("workerd/rust/cxx-integration-test/cxx-rust-integration-test.h");
30 }
31 
32 // Structures defined without any extern specifier are visible both to Rust and c++.
33 struct SharedStruct {
34 a: i32,
35 b: i32,
36 }
37 
38 extern "Rust" {
39 fn result_ok() -> Result<i32>;
40 fn result_error() -> Result<i32>;
41 
42 fn log_every_level();
43 
44 fn call_callback(callback: Pin<&mut TestCallback>, a: usize, b: usize) -> usize;
45 }
46 
47 extern "Rust" {
48 // Shared structures can be passed with full ownership
49 fn pass_shared_struct(s: SharedStruct) -> i32;
50 fn return_shared_struct() -> SharedStruct;
51 
52 // References can be safely passed from c++
53 fn pass_shared_struct_as_ref(s: &SharedStruct) -> i32;
54 fn pass_shared_struct_as_mut_ref(s: &mut SharedStruct);
55 
56 // Structs can be passed as pointers, but functions need to be unsafe then
57 unsafe fn pass_shared_struct_as_const_ptr(s: *const SharedStruct) -> i32;
58 unsafe fn pass_shared_struct_as_mut_ptr(s: *mut SharedStruct);
59 
60 // Box<T> is supported
61 fn pass_shared_struct_as_box(s: Box<SharedStruct>) -> i32;
62 fn return_shared_struct_as_box() -> Box<SharedStruct>;
63 }
64 
65 extern "Rust" {
66 // rust-defined structures can be exposed to c++ as opaque type.
67 type RustStruct;
68 
69 // rust needs to provide a way to access instances of the type.
70 fn rust_struct_new_box(name: &str) -> Box<RustStruct>;
71 
72 // c++ can interact with opaque structures using conventional functions and methods
73 fn get_name(self: &RustStruct) -> &str;
74 
75 // if there's only one type defined in extern block, then you can use self shorthand
76 fn set_name(&mut self, name: &str);
77 }
78 
79 extern "Rust" {
80 fn get_string() -> String;
81 fn get_str() -> &'static str;
82 }
83 
84 // test async
85 unsafe extern "C++" {
86 type UsizeCallback;
87 #[cxx_name = "operatorCALL"]
88 fn call(self: Pin<&mut UsizeCallback>, x: usize);
89 }
90 extern "Rust" {
91 fn async_immediate(callback: Pin<&'static mut UsizeCallback>);
92 fn async_sleep(callback: Pin<&'static mut UsizeCallback>);
93 }
94}
95 
96#[expect(clippy::unnecessary_wraps)]
97fn result_ok() -> Result<i32> {
98 Ok(42)
99}
100 
101fn result_error() -> Result<i32> {
102 Err(Error::other("test error"))
103}
104 
105fn log_every_level() {
106 trace!("rust_trace_message");
107 debug!("rust_debug_message");
108 info!("rust_info_message");
109 warn!("rust_warn_message");
110 error!("rust_error_message");
111}
112 
113fn call_callback(callback: Pin<&mut ffi::TestCallback>, a: usize, b: usize) -> usize {
114 callback.call(a, b)
115}
116 
117#[expect(clippy::needless_pass_by_value)]
118fn pass_shared_struct(s: ffi::SharedStruct) -> i32 {
119 s.a + s.b
120}
121 
122fn return_shared_struct() -> ffi::SharedStruct {
123 ffi::SharedStruct { a: 13, b: 29 }
124}
125 
126fn pass_shared_struct_as_ref(s: &ffi::SharedStruct) -> i32 {
127 s.a + s.b
128}
129 
130fn pass_shared_struct_as_mut_ref(s: &mut ffi::SharedStruct) {
131 s.a += s.b;
132 s.b = 0;
133}
134 
135unsafe fn pass_shared_struct_as_const_ptr(s: *const ffi::SharedStruct) -> i32 {
136 assert!(!s.is_null());
137 // SAFETY: Null check above ensures s is valid; the SharedStruct is valid for reads.
138 unsafe { (*s).a + (*s).b }
139}
140 
141unsafe fn pass_shared_struct_as_mut_ptr(s: *mut ffi::SharedStruct) {
142 // SAFETY: Caller guarantees s is a valid, non-null mutable pointer.
143 unsafe { (*s).a = 0 };
144 // SAFETY: Caller guarantees s is a valid, non-null mutable pointer.
145 unsafe { (*s).b = 0 };
146}
147 
148#[expect(clippy::boxed_local)] // clippy is right, but we want to test it anyway
149#[expect(clippy::needless_pass_by_value)]
150fn pass_shared_struct_as_box(s: Box<ffi::SharedStruct>) -> i32 {
151 s.a + s.b
152}
153 
154fn return_shared_struct_as_box() -> Box<ffi::SharedStruct> {
155 Box::new(ffi::SharedStruct { a: 1, b: 41 })
156}
157 
158struct RustStruct {
159 name: String,
160}
161 
162fn rust_struct_new_box(name: &str) -> Box<RustStruct> {
163 Box::new(RustStruct {
164 name: name.to_owned(),
165 })
166}
167 
168impl RustStruct {
169 fn get_name(&self) -> &str {
170 &self.name
171 }
172 
173 fn set_name(&mut self, name: &str) {
174 name.clone_into(&mut self.name);
175 }
176}
177 
178fn get_string() -> String {
179 "rust_string".to_owned()
180}
181 
182fn get_str() -> &'static str {
183 "rust_str"
184}
185 
186// SAFETY: UsizeCallback is only accessed from the Tokio runtime thread after being moved there.
187unsafe impl Send for ffi::UsizeCallback {}
188// SAFETY: UsizeCallback is only accessed from one thread at a time via Pin<&mut>.
189unsafe impl Sync for ffi::UsizeCallback {}
190 
191fn async_immediate(callback: Pin<&'static mut ffi::UsizeCallback>) {
192 cxx_integration::tokio::spawn(async move {
193 callback.call(42);
194 });
195}
196 
197fn async_sleep(callback: Pin<&'static mut ffi::UsizeCallback>) {
198 cxx_integration::tokio::spawn(async move {
199 tokio::time::sleep(Duration::from_millis(1)).await;
200 callback.call(42);
201 });
202}
203 
204#[cfg(all(test, feature = "sanitizer_address"))]
205mod tests {
206 use nix::sys::signal::Signal;
207 use safe_libc::expect_signal;
208 
209 #[test]
210 fn asan_stack_buffer_overflow() {
211 expect_signal!(Signal::SIGABRT, {
212 let xs = [0, 1, 2, 3];
213 // SAFETY: Intentional out-of-bounds access to trigger ASAN detection.
214 let _y = unsafe { *xs.as_ptr().offset(4) };
215 });
216 }
217}