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, usize)> {
94 unsafe { super::vtable::secondary_call_target(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 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<[usize; 10]> = std::sync::OnceLock::new();
273
274 fn mock_vtable() -> &'static [usize; 10] {
275 MOCK_VTABLE.get_or_init(|| {
276 [
277 mock_print_ln as *const () as usize, unused_slot as *const () as usize, mock_log_ln as *const () as usize, unused_slot as *const () as usize, mock_print_ln_u8 as *const () as usize, unused_slot as *const () as usize, mock_log_ln_u8 as *const () as usize, unused_slot as *const () as usize, 0,
286 0,
287 ]
288 })
289 }
290
291 fn make_mock_core() -> [usize; 32] {
298 let mut buf = [0usize; 32];
299 let vptr = mock_vtable().as_ptr() as usize;
300 let idx = usize::try_from(ILOGGER_OFFSET).expect("ILOGGER_OFFSET must be >= 0")
302 / std::mem::size_of::<usize>();
303 buf[idx] = vptr;
304 buf
305 }
306
307 #[test]
308 fn core_print_ln_calls_slot_0_at_logger_offset() {
309 let _g = TEST_LOCK.lock().unwrap();
310 reset_captures();
311 let mut core = make_mock_core();
312 let core_ptr = core.as_mut_ptr().cast::<ICore>();
313
314 let ok = unsafe { core_print_ln(core_ptr, "hello") };
315 assert!(ok, "core_print_ln must return true with a valid mock");
316
317 let c = last_capture();
318 assert_eq!(c.slot, Some(SLOT_PRINTLN));
319 assert_eq!(c.fmt.as_deref(), Some("%s"));
320 assert_eq!(c.message.as_deref(), Some("hello"));
321 assert_eq!(c.level, None, "printLn does not take a LogLevel");
322 }
323
324 #[test]
325 fn core_log_ln_calls_slot_2_with_level() {
326 let _g = TEST_LOCK.lock().unwrap();
327 reset_captures();
328 let mut core = make_mock_core();
329 let core_ptr = core.as_mut_ptr().cast::<ICore>();
330
331 let ok = unsafe { core_log_ln(core_ptr, LogLevel::Warning, "alert") };
332 assert!(ok);
333
334 let c = last_capture();
335 assert_eq!(c.slot, Some(SLOT_LOGLN));
336 assert_eq!(c.level, Some(LogLevel::Warning as c_int));
337 assert_eq!(c.fmt.as_deref(), Some("%s"));
338 assert_eq!(c.message.as_deref(), Some("alert"));
339 }
340
341 #[test]
342 fn core_print_ln_u8_calls_slot_4() {
343 let _g = TEST_LOCK.lock().unwrap();
344 reset_captures();
345 let mut core = make_mock_core();
346 let core_ptr = core.as_mut_ptr().cast::<ICore>();
347
348 let ok = unsafe { core_print_ln_u8(core_ptr, "hi") };
349 assert!(ok);
350
351 let c = last_capture();
352 assert_eq!(c.slot, Some(SLOT_PRINTLN_U8));
353 assert_eq!(c.message.as_deref(), Some("hi"));
354 }
355
356 #[test]
357 fn core_log_ln_u8_calls_slot_6_with_level() {
358 let _g = TEST_LOCK.lock().unwrap();
359 reset_captures();
360 let mut core = make_mock_core();
361 let core_ptr = core.as_mut_ptr().cast::<ICore>();
362
363 let ok = unsafe { core_log_ln_u8(core_ptr, LogLevel::Error, "critical failure") };
364 assert!(ok);
365
366 let c = last_capture();
367 assert_eq!(c.slot, Some(SLOT_LOGLN_U8));
368 assert_eq!(c.level, Some(LogLevel::Error as c_int));
369 assert_eq!(c.message.as_deref(), Some("critical failure"));
370 }
371
372 #[test]
373 fn all_log_fns_return_false_for_null_core() {
374 let _g = TEST_LOCK.lock().unwrap();
375 let nul = std::ptr::null_mut();
376 assert!(!unsafe { core_print_ln(nul, "x") });
377 assert!(!unsafe { core_log_ln(nul, LogLevel::Message, "x") });
378 assert!(!unsafe { core_print_ln_u8(nul, "x") });
379 assert!(!unsafe { core_log_ln_u8(nul, LogLevel::Message, "x") });
380 }
381
382 #[test]
383 fn log_fns_reject_message_with_interior_nul() {
384 let _g = TEST_LOCK.lock().unwrap();
385 let mut core = make_mock_core();
386 let core_ptr = core.as_mut_ptr().cast::<ICore>();
387 assert!(!unsafe { core_print_ln(core_ptr, "a\0b") });
389 assert!(!unsafe { core_log_ln(core_ptr, LogLevel::Message, "a\0b") });
390 }
391}