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