1use super::component::ICore;
43use std::ffi::CString;
44use std::os::raw::{c_char, c_int};
45
46#[cfg(target_env = "msvc")]
48const ILOGGER_OFFSET: isize = 56;
49
50#[cfg(not(target_env = "msvc"))]
51const ILOGGER_OFFSET: isize = 40;
52
53const SLOT_PRINTLN: usize = 0;
55
56const SLOT_LOGLN: usize = 2;
58
59const SLOT_PRINTLN_U8: usize = 4;
61
62const SLOT_LOGLN_U8: usize = 6;
64
65#[repr(C)]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum LogLevel {
69 Debug = 0,
70 Message = 1,
71 Warning = 2,
72 Error = 3,
73}
74
75type PrintLnFn = unsafe extern "C" fn(this: *mut u8, fmt: *const c_char, arg: *const c_char);
81
82type LogLnFn =
84 unsafe extern "C" fn(this: *mut u8, level: c_int, fmt: *const c_char, arg: *const c_char);
85
86unsafe fn logger_slot(core: *mut ICore, slot: usize) -> Option<(*mut u8, *const ())> {
94 unsafe { super::vtable::secondary_call_target_ptr(core.cast::<u8>(), ILOGGER_OFFSET, slot) }
95}
96
97pub unsafe fn core_print_ln(core: *mut ICore, message: &str) -> bool {
105 let Some((this, slot)) = (unsafe { logger_slot(core, SLOT_PRINTLN) }) else {
106 return false;
107 };
108 let Ok(msg) = CString::new(message) else {
109 return false;
110 };
111 let fmt = c"%s";
112 let f: PrintLnFn = unsafe { std::mem::transmute(slot) };
113 unsafe { f(this, fmt.as_ptr(), msg.as_ptr()) };
114 true
115}
116
117pub unsafe fn core_log_ln(core: *mut ICore, level: LogLevel, message: &str) -> bool {
125 let Some((this, slot)) = (unsafe { logger_slot(core, SLOT_LOGLN) }) else {
126 return false;
127 };
128 let Ok(msg) = CString::new(message) else {
129 return false;
130 };
131 let fmt = c"%s";
132 let f: LogLnFn = unsafe { std::mem::transmute(slot) };
133 unsafe { f(this, level as c_int, fmt.as_ptr(), msg.as_ptr()) };
134 true
135}
136
137pub unsafe fn core_print_ln_u8(core: *mut ICore, message: &str) -> bool {
146 let Some((this, slot)) = (unsafe { logger_slot(core, SLOT_PRINTLN_U8) }) else {
147 return false;
148 };
149 let Ok(msg) = CString::new(message) else {
150 return false;
151 };
152 let fmt = c"%s";
153 let f: PrintLnFn = unsafe { std::mem::transmute(slot) };
154 unsafe { f(this, fmt.as_ptr(), msg.as_ptr()) };
155 true
156}
157
158pub unsafe fn core_log_ln_u8(core: *mut ICore, level: LogLevel, message: &str) -> bool {
167 let Some((this, slot)) = (unsafe { logger_slot(core, SLOT_LOGLN_U8) }) else {
168 return false;
169 };
170 let Ok(msg) = CString::new(message) else {
171 return false;
172 };
173 let fmt = c"%s";
174 let f: LogLnFn = unsafe { std::mem::transmute(slot) };
175 unsafe { f(this, level as c_int, fmt.as_ptr(), msg.as_ptr()) };
176 true
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
191 use crate::omp::vtable::MockTable;
192 use std::ffi::CStr;
193 use std::sync::Mutex;
194
195 static TEST_LOCK: Mutex<()> = Mutex::new(());
196
197 #[derive(Default, Clone)]
198 struct Captured {
199 slot: Option<usize>,
200 level: Option<c_int>,
201 fmt: Option<String>,
202 message: Option<String>,
203 }
204
205 static CAPTURED: Mutex<Option<Captured>> = Mutex::new(None);
206
207 fn reset_captures() {
208 *CAPTURED.lock().unwrap() = Some(Captured::default());
209 }
210
211 fn last_capture() -> Captured {
212 CAPTURED.lock().unwrap().clone().unwrap_or_default()
213 }
214
215 fn cstr_to_string(ptr: *const c_char) -> Option<String> {
216 if ptr.is_null() {
217 return None;
218 }
219 unsafe { CStr::from_ptr(ptr) }
220 .to_str()
221 .ok()
222 .map(String::from)
223 }
224
225 unsafe extern "C" fn mock_print_ln(_this: *mut u8, fmt: *const c_char, arg: *const c_char) {
226 let mut guard = CAPTURED.lock().unwrap();
227 let c = guard.as_mut().unwrap();
228 c.slot = Some(SLOT_PRINTLN);
229 c.fmt = cstr_to_string(fmt);
230 c.message = cstr_to_string(arg);
231 }
232
233 unsafe extern "C" fn mock_log_ln(
234 _this: *mut u8,
235 level: c_int,
236 fmt: *const c_char,
237 arg: *const c_char,
238 ) {
239 let mut guard = CAPTURED.lock().unwrap();
240 let c = guard.as_mut().unwrap();
241 c.slot = Some(SLOT_LOGLN);
242 c.level = Some(level);
243 c.fmt = cstr_to_string(fmt);
244 c.message = cstr_to_string(arg);
245 }
246
247 unsafe extern "C" fn mock_print_ln_u8(_this: *mut u8, fmt: *const c_char, arg: *const c_char) {
248 let mut guard = CAPTURED.lock().unwrap();
249 let c = guard.as_mut().unwrap();
250 c.slot = Some(SLOT_PRINTLN_U8);
251 c.fmt = cstr_to_string(fmt);
252 c.message = cstr_to_string(arg);
253 }
254
255 unsafe extern "C" fn mock_log_ln_u8(
256 _this: *mut u8,
257 level: c_int,
258 fmt: *const c_char,
259 arg: *const c_char,
260 ) {
261 let mut guard = CAPTURED.lock().unwrap();
262 let c = guard.as_mut().unwrap();
263 c.slot = Some(SLOT_LOGLN_U8);
264 c.level = Some(level);
265 c.fmt = cstr_to_string(fmt);
266 c.message = cstr_to_string(arg);
267 }
268
269 unsafe extern "C" fn unused_slot() {}
270
271 static MOCK_VTABLE: std::sync::OnceLock<MockTable<10>> = std::sync::OnceLock::new();
274
275 fn mock_vtable() -> &'static [*const (); 10] {
276 &MOCK_VTABLE
277 .get_or_init(|| {
278 MockTable([
279 mock_print_ln as *const (), unused_slot as *const (), mock_log_ln as *const (), unused_slot as *const (), mock_print_ln_u8 as *const (), unused_slot as *const (), mock_log_ln_u8 as *const (), unused_slot as *const (), std::ptr::null(),
288 std::ptr::null(),
289 ])
290 })
291 .0
292 }
293
294 fn make_mock_core() -> [*const (); 32] {
301 let mut buf = [std::ptr::null::<()>(); 32];
302 let vptr = mock_vtable().as_ptr().cast::<()>();
303 let idx = usize::try_from(ILOGGER_OFFSET).expect("ILOGGER_OFFSET must be >= 0")
305 / std::mem::size_of::<*const ()>();
306 buf[idx] = vptr;
307 buf
308 }
309
310 #[test]
311 fn core_print_ln_calls_slot_0_at_logger_offset() {
312 let _g = TEST_LOCK.lock().unwrap();
313 reset_captures();
314 let mut core = make_mock_core();
315 let core_ptr = core.as_mut_ptr().cast::<ICore>();
316
317 let ok = unsafe { core_print_ln(core_ptr, "hello") };
318 assert!(ok, "core_print_ln must return true with a valid mock");
319
320 let c = last_capture();
321 assert_eq!(c.slot, Some(SLOT_PRINTLN));
322 assert_eq!(c.fmt.as_deref(), Some("%s"));
323 assert_eq!(c.message.as_deref(), Some("hello"));
324 assert_eq!(c.level, None, "printLn does not take a LogLevel");
325 }
326
327 #[test]
328 fn core_log_ln_calls_slot_2_with_level() {
329 let _g = TEST_LOCK.lock().unwrap();
330 reset_captures();
331 let mut core = make_mock_core();
332 let core_ptr = core.as_mut_ptr().cast::<ICore>();
333
334 let ok = unsafe { core_log_ln(core_ptr, LogLevel::Warning, "alert") };
335 assert!(ok);
336
337 let c = last_capture();
338 assert_eq!(c.slot, Some(SLOT_LOGLN));
339 assert_eq!(c.level, Some(LogLevel::Warning as c_int));
340 assert_eq!(c.fmt.as_deref(), Some("%s"));
341 assert_eq!(c.message.as_deref(), Some("alert"));
342 }
343
344 #[test]
345 fn core_print_ln_u8_calls_slot_4() {
346 let _g = TEST_LOCK.lock().unwrap();
347 reset_captures();
348 let mut core = make_mock_core();
349 let core_ptr = core.as_mut_ptr().cast::<ICore>();
350
351 let ok = unsafe { core_print_ln_u8(core_ptr, "hi") };
352 assert!(ok);
353
354 let c = last_capture();
355 assert_eq!(c.slot, Some(SLOT_PRINTLN_U8));
356 assert_eq!(c.message.as_deref(), Some("hi"));
357 }
358
359 #[test]
360 fn core_log_ln_u8_calls_slot_6_with_level() {
361 let _g = TEST_LOCK.lock().unwrap();
362 reset_captures();
363 let mut core = make_mock_core();
364 let core_ptr = core.as_mut_ptr().cast::<ICore>();
365
366 let ok = unsafe { core_log_ln_u8(core_ptr, LogLevel::Error, "critical failure") };
367 assert!(ok);
368
369 let c = last_capture();
370 assert_eq!(c.slot, Some(SLOT_LOGLN_U8));
371 assert_eq!(c.level, Some(LogLevel::Error as c_int));
372 assert_eq!(c.message.as_deref(), Some("critical failure"));
373 }
374
375 #[test]
376 fn all_log_fns_return_false_for_null_core() {
377 let _g = TEST_LOCK.lock().unwrap();
378 let nul = std::ptr::null_mut();
379 assert!(!unsafe { core_print_ln(nul, "x") });
380 assert!(!unsafe { core_log_ln(nul, LogLevel::Message, "x") });
381 assert!(!unsafe { core_print_ln_u8(nul, "x") });
382 assert!(!unsafe { core_log_ln_u8(nul, LogLevel::Message, "x") });
383 }
384
385 #[test]
386 fn log_fns_reject_message_with_interior_nul() {
387 let _g = TEST_LOCK.lock().unwrap();
388 let mut core = make_mock_core();
389 let core_ptr = core.as_mut_ptr().cast::<ICore>();
390 assert!(!unsafe { core_print_ln(core_ptr, "a\0b") });
392 assert!(!unsafe { core_log_ln(core_ptr, LogLevel::Message, "a\0b") });
393 }
394}