From 4c139263cb4625624844efd22e19b5e0a47def9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benoi=CC=82t=20Rouleau?= Date: Sat, 10 Oct 2026 02:03:09 -0400 Subject: [PATCH] log: replace NUL bytes in a message instead of panicking Android's log and the browser's console take C strings, and `__private_api_log_lit` built them with `CString::new(message).unwrap_or_else(|_| panic!())`. So a message holding a NUL byte panicked inside `warn!` or `error!`, with no message of its own, and never reached the log. That happens whenever a message quotes data the app doesn't control, such as the text of a file it failed to parse. Each NUL is now replaced with U+FFFD, the replacement character, so the rest of the message still reaches the log. Co-Authored-By: Claude Opus 5.5 --- src/log.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/log.rs b/src/log.rs index 429df2995..52a9d55ff 100644 --- a/src/log.rs +++ b/src/log.rs @@ -194,7 +194,6 @@ pub fn __private_api_log_lit( &(_target, _module_path, _file, _line): &(&str, &'static str, &'static str, u32), ) { use crate::native::wasm; - use std::ffi::CString; let log_fn = match level { Level::Debug => wasm::console_debug, @@ -203,7 +202,7 @@ pub fn __private_api_log_lit( Level::Trace => wasm::console_debug, Level::Error => wasm::console_error, }; - let msg = CString::new(message).unwrap_or_else(|_| panic!()); + let msg = c_string(message); unsafe { log_fn(msg.as_ptr()) }; } @@ -214,8 +213,6 @@ pub fn __private_api_log_lit( level: Level, &(_target, _module_path, _file, _line): &(&str, &'static str, &'static str, u32), ) { - use std::ffi::CString; - let log_fn = match level { Level::Debug => crate::native::android::console_debug, Level::Warn => crate::native::android::console_warn, @@ -223,11 +220,18 @@ pub fn __private_api_log_lit( Level::Trace => crate::native::android::console_debug, Level::Error => crate::native::android::console_error, }; - let msg = CString::new(message).unwrap_or_else(|_| panic!()); + let msg = c_string(message); unsafe { log_fn(msg.as_ptr()) }; } +/// Android's log and the browser's console take C strings, which end at the +/// first NUL, so each NUL in `message` is replaced instead of panicking. +#[cfg(any(target_arch = "wasm32", target_os = "android", test))] +fn c_string(message: &str) -> std::ffi::CString { + std::ffi::CString::new(message.replace('\0', "\u{FFFD}")).unwrap() +} + #[cfg(target_os = "ios")] pub fn __private_api_log_lit( message: &str, @@ -254,3 +258,8 @@ fn test_logs() { error!("info"); error!("info: {}", 1); } + +#[test] +fn test_c_string_replaces_nul() { + assert_eq!(c_string("a\0b").to_str(), Ok("a\u{FFFD}b")); +}