From c14b3ef890a714c1de8c074eb7fa0084c025f9e0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 00:56:16 +0200 Subject: [PATCH 01/12] [NativeAOT] Add printf-style native logging Introduce printf-style native logging and abort helpers while preserving the existing std::format APIs for MonoVM and CoreCLR. Migrate the NativeAOT-specific formatted call sites to the new primitives. Refs #12139 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 187b207a-083b-461e-9071-e9aab61c9d07 --- src/native/clr/include/shared/log_types.hh | 8 ++ src/native/clr/shared/helpers.cc | 20 ++++- src/native/clr/shared/log_functions.cc | 85 +++++++++++++++++-- src/native/common/include/shared/helpers.hh | 3 + .../nativeaot/host/bridge-processing.cc | 16 ++-- src/native/nativeaot/host/host.cc | 4 +- 6 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/native/clr/include/shared/log_types.hh b/src/native/clr/include/shared/log_types.hh index 7aef6e20456..8e8d0811e45 100644 --- a/src/native/clr/include/shared/log_types.hh +++ b/src/native/clr/include/shared/log_types.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,6 +48,13 @@ namespace xamarin::android { // A slightly faster alternative to other log functions as it doesn't parse the message // for format placeholders nor it uses variable arguments void log_write (LogCategories category, LogLevel level, const char *message) noexcept; + void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; + void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_fatalf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept diff --git a/src/native/clr/shared/helpers.cc b/src/native/clr/shared/helpers.cc index 7850592af5a..da509e5fb73 100644 --- a/src/native/clr/shared/helpers.cc +++ b/src/native/clr/shared/helpers.cc @@ -7,11 +7,24 @@ using namespace xamarin::android; +[[noreturn]] void +Helpers::abort_applicationf (LogCategories category, std::source_location sloc, const char *format, ...) noexcept +{ + char *message = nullptr; + const char *safe_format = format == nullptr ? "" : format; + va_list args; + va_start (args, format); + int ret = vasprintf (&message, safe_format, args); + va_end (args); + + abort_application (category, ret < 0 ? safe_format : message, true, sloc); +} + [[noreturn]] void Helpers::abort_application (LogCategories category, const char *message, bool log_location, std::source_location sloc) noexcept { // Log it, but also... - log_fatal (category, "{}", message); + log_write (category, LogLevel::Fatal, message); // ...let android include it in the tombstone, debuggerd output, stack trace etc android_set_abort_message (message); @@ -33,9 +46,10 @@ Helpers::abort_application (LogCategories category, const char *message, bool lo } } - log_fatal ( + log_writef ( category, - "Abort at {}:{}:{} ('{}')", + LogLevel::Fatal, + "Abort at %s:%u:%u ('%s')", file_name, sloc.line (), sloc.column (), diff --git a/src/native/clr/shared/log_functions.cc b/src/native/clr/shared/log_functions.cc index acd5e705c06..0c097edaed7 100644 --- a/src/native/clr/shared/log_functions.cc +++ b/src/native/clr/shared/log_functions.cc @@ -55,6 +55,17 @@ namespace { }; constexpr size_t loglevel_map_max_index = (sizeof(loglevel_map) / sizeof(android_LogPriority)) - 1; + + [[gnu::always_inline]] + auto priority_for_level (LogLevel level) noexcept -> android_LogPriority + { + size_t map_index = static_cast(level); + if (map_index > loglevel_map_max_index) { + return DEFAULT_PRIORITY; + } + + return loglevel_map[map_index]; + } } unsigned int log_categories = LOG_NONE; @@ -117,15 +128,75 @@ namespace xamarin::android { void log_write (LogCategories category, LogLevel level, const char *message) noexcept { - size_t map_index = static_cast(level); - android_LogPriority priority; + __android_log_write (priority_for_level (level), category_name (category), message); + } - if (map_index > loglevel_map_max_index) { - priority = DEFAULT_PRIORITY; - } else { - priority = loglevel_map[map_index]; + void + log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept + { + const char *safe_format = format == nullptr ? "" : format; + __android_log_vprint (priority_for_level (level), category_name (category), safe_format, args); + } + + void + log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, level, format, args); + va_end (args); + } + + void + log_debugf (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; + } + + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Debug, format, args); + va_end (args); + } + + void + log_infof (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; } - __android_log_write (priority, category_name (category), message); + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Info, format, args); + va_end (args); + } + + void + log_warnf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Warn, format, args); + va_end (args); + } + + void + log_errorf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Error, format, args); + va_end (args); + } + + void + log_fatalf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Fatal, format, args); + va_end (args); } } diff --git a/src/native/common/include/shared/helpers.hh b/src/native/common/include/shared/helpers.hh index 22e29b40f25..f1ec1fb6ab4 100644 --- a/src/native/common/include/shared/helpers.hh +++ b/src/native/common/include/shared/helpers.hh @@ -56,6 +56,9 @@ namespace xamarin::android [[noreturn]] static void abort_application (LogCategories category, const char *message, bool log_location = true, std::source_location sloc = std::source_location::current ()) noexcept; + [[noreturn]] + static void abort_applicationf (LogCategories category, std::source_location sloc, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + [[noreturn]] static void abort_application (LogCategories category, std::string const& message, bool log_location = true, std::source_location sloc = std::source_location::current ()) noexcept { diff --git a/src/native/nativeaot/host/bridge-processing.cc b/src/native/nativeaot/host/bridge-processing.cc index b87a49b431b..eb636515d6f 100644 --- a/src/native/nativeaot/host/bridge-processing.cc +++ b/src/native/nativeaot/host/bridge-processing.cc @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -21,16 +19,12 @@ void BridgeProcessing::naot_initialize_on_runtime_init (JNIEnv *env) noexcept GCUserPeerable_jiClearManagedReferences = env->GetMethodID (GCUserPeerable_class, "jiClearManagedReferences", "()V"); if (GCUserPeerable_jiAddManagedReference == nullptr || GCUserPeerable_jiClearManagedReferences == nullptr) [[unlikely]] { - constexpr auto ABSENT = "absent"sv; - constexpr auto PRESENT = "present"sv; - - Helpers::abort_application ( + Helpers::abort_applicationf ( LOG_DEFAULT, - std::format ( - "Failed to find GCUserPeerable method(s): jiAddManagedReference ({}); jiClearManagedReferences ({})"sv, - GCUserPeerable_jiAddManagedReference == nullptr ? ABSENT : PRESENT, - GCUserPeerable_jiClearManagedReferences == nullptr ? ABSENT : PRESENT - ) + std::source_location::current (), + "Failed to find GCUserPeerable method(s): jiAddManagedReference (%s); jiClearManagedReferences (%s)", + GCUserPeerable_jiAddManagedReference == nullptr ? "absent" : "present", + GCUserPeerable_jiClearManagedReferences == nullptr ? "absent" : "present" ); } } diff --git a/src/native/nativeaot/host/host.cc b/src/native/nativeaot/host/host.cc index b6d87483c78..7258f963785 100644 --- a/src/native/nativeaot/host/host.cc +++ b/src/native/nativeaot/host/host.cc @@ -33,9 +33,9 @@ auto HostCommon::Java_JNI_OnLoad (JavaVM *vm, void *reserved) noexcept -> jint if (__jni_on_load_handler_count > 0) { for (uint32_t i = 0; i < __jni_on_load_handler_count; i++) { - log_debug ( + log_debugf ( LOG_ASSEMBLY, - "Calling JNI on-load init func '{}' ({:p})", + "Calling JNI on-load init func '%s' (%p)", optional_string (__jni_on_load_handler_names[i]), reinterpret_cast(__jni_on_load_handlers[i]) ); From 90e6fb67de6dab87b6cf755e5fb48f7d17ddaafa Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:18:55 +0200 Subject: [PATCH 02/12] [native] Test printf-style logging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../NativeLoggingTests.cs | 50 +++++++++++ .../NativeLogging/include/android/log.h | 26 ++++++ .../NativeLogging/include/constants.hh | 22 +++++ .../NativeLogging/log-functions-tests.cc | 83 +++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs new file mode 100644 index 00000000000..0454820a571 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using System; +using System.IO; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + public class NativeLoggingTests : BaseTest + { + [Test] + public void PrintfLoggingForwardsArgumentsAndSkipsDisabledCategories () + { + if (IsWindows) { + Assert.Ignore ("The native logging test requires a host C++ compiler."); + } + + var testDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "Xamarin.Android.Build.Tasks", "Tests", "Xamarin.Android.Build.Tests", "Resources", "NativeLogging"); + var nativeDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "native"); + var outputDirectory = Path.Combine (Root, TestName); + var output = Path.Combine (outputDirectory, "native-logging-tests"); + Directory.CreateDirectory (outputDirectory); + + var compiler = Environment.GetEnvironmentVariable ("CXX"); + if (string.IsNullOrEmpty (compiler)) { + compiler = "clang++"; + } + + var arguments = string.Join (" ", new [] { + "-std=c++23", + "-Wall", + "-Wextra", + "-Werror", + $"-I\"{Path.Combine (testDirectory, "include")}\"", + $"-I\"{Path.Combine (nativeDirectory, "common", "include")}\"", + $"-I\"{Path.Combine (nativeDirectory, "clr", "include")}\"", + $"-I\"{Path.Combine (XABuildPaths.TopDirectory, "external", "Java.Interop", "src", "java-interop")}\"", + $"\"{Path.Combine (testDirectory, "log-functions-tests.cc")}\"", + $"\"{Path.Combine (nativeDirectory, "clr", "shared", "log_functions.cc")}\"", + $"-o \"{output}\"", + }); + + var (compileExitCode, compileOutput, compileError) = RunProcessWithExitCode (compiler, arguments); + Assert.That (compileExitCode, Is.EqualTo (0), $"{compileOutput}{Environment.NewLine}{compileError}"); + + var (testExitCode, testOutput, testError) = RunProcessWithExitCode (output, ""); + Assert.That (testExitCode, Is.EqualTo (0), $"{testOutput}{Environment.NewLine}{testError}"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h new file mode 100644 index 00000000000..61d3d0a79e7 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum android_LogPriority { + ANDROID_LOG_UNKNOWN = 0, + ANDROID_LOG_DEFAULT, + ANDROID_LOG_VERBOSE, + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, + ANDROID_LOG_FATAL, + ANDROID_LOG_SILENT, +} android_LogPriority; + +int __android_log_write (int priority, const char *tag, const char *text); +int __android_log_vprint (int priority, const char *tag, const char *format, va_list args); + +#ifdef __cplusplus +} +#endif diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh new file mode 100644 index 00000000000..cb5bbb465da --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace xamarin::android { + class Constants + { + public: + static constexpr std::string_view LOG_CATEGORY_NAME_NONE { "*none*" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID { "monodroid" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_ASSEMBLY { "monodroid-assembly" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_DEBUG { "monodroid-debug" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GC { "monodroid-gc" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GREF { "monodroid-gref" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_LREF { "monodroid-lref" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_TIMING { "monodroid-timing" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_BUNDLE { "monodroid-bundle" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETWORK { "monodroid-network" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETLINK { "monodroid-netlink" }; + static constexpr std::string_view LOG_CATEGORY_NAME_ERROR { "*error*" }; + }; +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc new file mode 100644 index 00000000000..a39da50ac20 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc @@ -0,0 +1,83 @@ +#include +#include +#include + +#include +#include + +namespace { + constexpr size_t MESSAGE_SIZE = 256; + + char last_message [MESSAGE_SIZE]; + int last_priority; + int log_count; + + auto fail (const char *message) noexcept -> int + { + std::fprintf (stderr, "%s\n", message); + return 1; + } + + void reset_log () noexcept + { + std::memset (last_message, 0, sizeof (last_message)); + last_priority = -1; + log_count = 0; + } +} + +extern "C" int +__android_log_write (int priority, const char*, const char *text) +{ + last_priority = priority; + log_count++; + return std::snprintf (last_message, sizeof (last_message), "%s", text); +} + +extern "C" int +__android_log_vprint (int priority, const char*, const char *format, va_list args) +{ + last_priority = priority; + log_count++; + return std::vsnprintf (last_message, sizeof (last_message), format, args); +} + +int +main () +{ + using namespace xamarin::android; + + const char *text = "forwarded"; + const void *pointer = &log_count; + char expected [MESSAGE_SIZE]; + std::snprintf (expected, sizeof (expected), "%s %p %d", text, pointer, -42); + + reset_log (); + log_writef (LOG_ASSEMBLY, LogLevel::Info, "%s %p %d", text, pointer, -42); + if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, expected) != 0) { + return fail ("printf arguments were not forwarded to the Android logging API"); + } + + log_categories = LOG_NONE; + reset_log (); + log_debugf (LOG_ASSEMBLY, "%s", "disabled debug"); + log_infof (LOG_ASSEMBLY, "%s", "disabled info"); + if (log_count != 0 || last_message[0] != '\0') { + return fail ("disabled debug or info logging invoked formatting"); + } + + log_categories = LOG_ASSEMBLY; + reset_log (); + log_debugf (LOG_ASSEMBLY, "%d", 7); + if (log_count != 1 || last_priority != ANDROID_LOG_DEBUG || std::strcmp (last_message, "7") != 0) { + return fail ("enabled debug logging did not format its message"); + } + + reset_log (); + log_infof (LOG_ASSEMBLY, "%s", "enabled info"); + if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, "enabled info") != 0) { + return fail ("enabled info logging did not format its message"); + } + + return 0; +} From 2cb0c4e946266989db2be6dc0291c0a368e8c4ee Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:20:53 +0200 Subject: [PATCH 03/12] [Tests] Follow Android string helper convention Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs index 0454820a571..508320d35ee 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs @@ -22,7 +22,7 @@ public void PrintfLoggingForwardsArgumentsAndSkipsDisabledCategories () Directory.CreateDirectory (outputDirectory); var compiler = Environment.GetEnvironmentVariable ("CXX"); - if (string.IsNullOrEmpty (compiler)) { + if (compiler.IsNullOrEmpty ()) { compiler = "clang++"; } From 4a27f681ccbb2cbf3720f3f4895d379248d0f8a2 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:24:44 +0200 Subject: [PATCH 04/12] [Tests] Import Android string helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs index 508320d35ee..eed0d6905b3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using System; using System.IO; +using Xamarin.Android.Tools; using Xamarin.ProjectTools; namespace Xamarin.Android.Build.Tests From ee07100fec749d4a5a4a05e89723601a531f58bb Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:48:28 +0200 Subject: [PATCH 05/12] [Tests] Remove native logging test harness Keep the printf logging PR focused on the runtime implementation instead of introducing new host-native logging test infrastructure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../NativeLoggingTests.cs | 51 ------------ .../NativeLogging/include/android/log.h | 26 ------ .../NativeLogging/include/constants.hh | 22 ----- .../NativeLogging/log-functions-tests.cc | 83 ------------------- 4 files changed, 182 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs deleted file mode 100644 index eed0d6905b3..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using NUnit.Framework; -using System; -using System.IO; -using Xamarin.Android.Tools; -using Xamarin.ProjectTools; - -namespace Xamarin.Android.Build.Tests -{ - [TestFixture] - public class NativeLoggingTests : BaseTest - { - [Test] - public void PrintfLoggingForwardsArgumentsAndSkipsDisabledCategories () - { - if (IsWindows) { - Assert.Ignore ("The native logging test requires a host C++ compiler."); - } - - var testDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "Xamarin.Android.Build.Tasks", "Tests", "Xamarin.Android.Build.Tests", "Resources", "NativeLogging"); - var nativeDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "native"); - var outputDirectory = Path.Combine (Root, TestName); - var output = Path.Combine (outputDirectory, "native-logging-tests"); - Directory.CreateDirectory (outputDirectory); - - var compiler = Environment.GetEnvironmentVariable ("CXX"); - if (compiler.IsNullOrEmpty ()) { - compiler = "clang++"; - } - - var arguments = string.Join (" ", new [] { - "-std=c++23", - "-Wall", - "-Wextra", - "-Werror", - $"-I\"{Path.Combine (testDirectory, "include")}\"", - $"-I\"{Path.Combine (nativeDirectory, "common", "include")}\"", - $"-I\"{Path.Combine (nativeDirectory, "clr", "include")}\"", - $"-I\"{Path.Combine (XABuildPaths.TopDirectory, "external", "Java.Interop", "src", "java-interop")}\"", - $"\"{Path.Combine (testDirectory, "log-functions-tests.cc")}\"", - $"\"{Path.Combine (nativeDirectory, "clr", "shared", "log_functions.cc")}\"", - $"-o \"{output}\"", - }); - - var (compileExitCode, compileOutput, compileError) = RunProcessWithExitCode (compiler, arguments); - Assert.That (compileExitCode, Is.EqualTo (0), $"{compileOutput}{Environment.NewLine}{compileError}"); - - var (testExitCode, testOutput, testError) = RunProcessWithExitCode (output, ""); - Assert.That (testExitCode, Is.EqualTo (0), $"{testOutput}{Environment.NewLine}{testError}"); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h deleted file mode 100644 index 61d3d0a79e7..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum android_LogPriority { - ANDROID_LOG_UNKNOWN = 0, - ANDROID_LOG_DEFAULT, - ANDROID_LOG_VERBOSE, - ANDROID_LOG_DEBUG, - ANDROID_LOG_INFO, - ANDROID_LOG_WARN, - ANDROID_LOG_ERROR, - ANDROID_LOG_FATAL, - ANDROID_LOG_SILENT, -} android_LogPriority; - -int __android_log_write (int priority, const char *tag, const char *text); -int __android_log_vprint (int priority, const char *tag, const char *format, va_list args); - -#ifdef __cplusplus -} -#endif diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh deleted file mode 100644 index cb5bbb465da..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -namespace xamarin::android { - class Constants - { - public: - static constexpr std::string_view LOG_CATEGORY_NAME_NONE { "*none*" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID { "monodroid" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_ASSEMBLY { "monodroid-assembly" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_DEBUG { "monodroid-debug" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GC { "monodroid-gc" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GREF { "monodroid-gref" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_LREF { "monodroid-lref" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_TIMING { "monodroid-timing" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_BUNDLE { "monodroid-bundle" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETWORK { "monodroid-network" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETLINK { "monodroid-netlink" }; - static constexpr std::string_view LOG_CATEGORY_NAME_ERROR { "*error*" }; - }; -} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc deleted file mode 100644 index a39da50ac20..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include - -#include -#include - -namespace { - constexpr size_t MESSAGE_SIZE = 256; - - char last_message [MESSAGE_SIZE]; - int last_priority; - int log_count; - - auto fail (const char *message) noexcept -> int - { - std::fprintf (stderr, "%s\n", message); - return 1; - } - - void reset_log () noexcept - { - std::memset (last_message, 0, sizeof (last_message)); - last_priority = -1; - log_count = 0; - } -} - -extern "C" int -__android_log_write (int priority, const char*, const char *text) -{ - last_priority = priority; - log_count++; - return std::snprintf (last_message, sizeof (last_message), "%s", text); -} - -extern "C" int -__android_log_vprint (int priority, const char*, const char *format, va_list args) -{ - last_priority = priority; - log_count++; - return std::vsnprintf (last_message, sizeof (last_message), format, args); -} - -int -main () -{ - using namespace xamarin::android; - - const char *text = "forwarded"; - const void *pointer = &log_count; - char expected [MESSAGE_SIZE]; - std::snprintf (expected, sizeof (expected), "%s %p %d", text, pointer, -42); - - reset_log (); - log_writef (LOG_ASSEMBLY, LogLevel::Info, "%s %p %d", text, pointer, -42); - if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, expected) != 0) { - return fail ("printf arguments were not forwarded to the Android logging API"); - } - - log_categories = LOG_NONE; - reset_log (); - log_debugf (LOG_ASSEMBLY, "%s", "disabled debug"); - log_infof (LOG_ASSEMBLY, "%s", "disabled info"); - if (log_count != 0 || last_message[0] != '\0') { - return fail ("disabled debug or info logging invoked formatting"); - } - - log_categories = LOG_ASSEMBLY; - reset_log (); - log_debugf (LOG_ASSEMBLY, "%d", 7); - if (log_count != 1 || last_priority != ANDROID_LOG_DEBUG || std::strcmp (last_message, "7") != 0) { - return fail ("enabled debug logging did not format its message"); - } - - reset_log (); - log_infof (LOG_ASSEMBLY, "%s", "enabled info"); - if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, "enabled info") != 0) { - return fail ("enabled info logging did not format its message"); - } - - return 0; -} From 7a34259f35b143e9e3ee9a9c2a719a53a942dde9 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 13:31:52 +0200 Subject: [PATCH 06/12] [CoreCLR/NativeAOT] Migrate configuration diagnostics to printf Convert host environment and integer parsing diagnostics to #12140 printf helpers while preserving MonoVM formatting branches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../clr/include/host/host-environment.hh | 6 ++--- .../common/include/runtime-base/strings.hh | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/native/clr/include/host/host-environment.hh b/src/native/clr/include/host/host-environment.hh index af8976f2f83..d0c5a7ab871 100644 --- a/src/native/clr/include/host/host-environment.hh +++ b/src/native/clr/include/host/host-environment.hh @@ -48,7 +48,7 @@ namespace xamarin::android { static void set_system_property (const char *name, const char *value) noexcept { // TODO: should we **actually** try to set the system property here? Would that even work? Needs testing - log_debug (LOG_DEFAULT, " System property {} = '{}'", optional_string (name), optional_string (value)); + log_debugf (LOG_DEFAULT, " System property %s = '%s'", optional_string (name), optional_string (value)); } [[gnu::flatten, gnu::always_inline]] @@ -94,10 +94,10 @@ namespace xamarin::android { static_local_string dir (home_len + relative_path.length ()); Util::path_combine (dir, home.get_string_view (), relative_path); - log_debug (LOG_DEFAULT, "Creating XDG directory: {}"sv, optional_string (dir.get ())); + log_debugf (LOG_DEFAULT, "Creating XDG directory: %s", optional_string (dir.get ())); int rv = Util::create_directory (dir.get (), Constants::DEFAULT_DIRECTORY_MODE); if (rv < 0 && errno != EEXIST) { - log_warn (LOG_DEFAULT, "Failed to create XDG directory {}. {}"sv, optional_string (dir.get ()), strerror (errno)); + log_warnf (LOG_DEFAULT, "Failed to create XDG directory %s. %s", optional_string (dir.get ()), strerror (errno)); } if (!environment_variable_name.empty ()) { diff --git a/src/native/common/include/runtime-base/strings.hh b/src/native/common/include/runtime-base/strings.hh index 8e6bf8cbaea..3d408811a37 100644 --- a/src/native/common/include/runtime-base/strings.hh +++ b/src/native/common/include/runtime-base/strings.hh @@ -11,6 +11,9 @@ #include #include +#if !defined(XA_HOST_MONOVM) +#include +#endif #if defined(XA_HOST_MONOVM) #include @@ -205,7 +208,11 @@ namespace xamarin::android { } if (!can_access (start_index)) { +#if defined(XA_HOST_MONOVM) log_error (LOG_DEFAULT, "Cannot convert string to integer, index {} is out of range", start_index); +#else + log_errorf (LOG_DEFAULT, "Cannot convert string to integer, index %zu is out of range", start_index); +#endif return false; } @@ -229,17 +236,35 @@ namespace xamarin::android { } if (out_of_range || errno == ERANGE) { +#if defined(XA_HOST_MONOVM) log_error (LOG_DEFAULT, "Value {} is out of range of this type ({}..{})", reinterpret_cast(s), static_cast(min), static_cast(max)); +#else + log_errorf ( + LOG_DEFAULT, + "Value %s is out of range of this type (%lld..%llu)", + reinterpret_cast(s), + static_cast(static_cast(min)), + static_cast(static_cast(max)) + ); +#endif return false; } if (endp == s) { +#if defined(XA_HOST_MONOVM) log_error (LOG_DEFAULT, "Value {} does not represent a base {} integer", reinterpret_cast(s), base); +#else + log_errorf (LOG_DEFAULT, "Value %s does not represent a base %d integer", reinterpret_cast(s), base); +#endif return false; } if (*endp != '\0') { +#if defined(XA_HOST_MONOVM) log_error (LOG_DEFAULT, "Value {} has non-numeric characters at the end", reinterpret_cast(s)); +#else + log_errorf (LOG_DEFAULT, "Value %s has non-numeric characters at the end", reinterpret_cast(s)); +#endif return false; } From a1bf4b7511b762a97174ecc1a83c14db409e00b4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 13:45:40 +0200 Subject: [PATCH 07/12] Address printf logging review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/clr/include/shared/log_types.hh | 1 - src/native/clr/shared/helpers.cc | 1 + src/native/clr/shared/log_functions.cc | 8 -------- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/native/clr/include/shared/log_types.hh b/src/native/clr/include/shared/log_types.hh index 8e8d0811e45..b87347ad0a1 100644 --- a/src/native/clr/include/shared/log_types.hh +++ b/src/native/clr/include/shared/log_types.hh @@ -54,7 +54,6 @@ namespace xamarin::android { void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_fatalf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept diff --git a/src/native/clr/shared/helpers.cc b/src/native/clr/shared/helpers.cc index da509e5fb73..5c95342b1af 100644 --- a/src/native/clr/shared/helpers.cc +++ b/src/native/clr/shared/helpers.cc @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/src/native/clr/shared/log_functions.cc b/src/native/clr/shared/log_functions.cc index 0c097edaed7..2f3db78abe7 100644 --- a/src/native/clr/shared/log_functions.cc +++ b/src/native/clr/shared/log_functions.cc @@ -191,12 +191,4 @@ namespace xamarin::android { va_end (args); } - void - log_fatalf (LogCategories category, const char *format, ...) noexcept - { - va_list args; - va_start (args, format); - log_writev (category, LogLevel::Fatal, format, args); - va_end (args); - } } From 09f0690daaec89f8a061b4bb3fee5ed58e150893 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 14:00:38 +0200 Subject: [PATCH 08/12] Support printf logging helpers on MonoVM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/mono/shared/log_functions.cc | 77 ++++++++++++++++++++++--- src/native/mono/shared/log_types.hh | 7 +++ 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/native/mono/shared/log_functions.cc b/src/native/mono/shared/log_functions.cc index 394ba2a1927..16e8eda7525 100644 --- a/src/native/mono/shared/log_functions.cc +++ b/src/native/mono/shared/log_functions.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -102,19 +103,81 @@ static constexpr android_LogPriority loglevel_map[] = { static constexpr size_t loglevel_map_max_index = (sizeof(loglevel_map) / sizeof(android_LogPriority)) - 1; +static auto +priority_for_level (xamarin::android::LogLevel level) noexcept -> android_LogPriority +{ + size_t map_index = static_cast(level); + if (map_index > loglevel_map_max_index) { + return DEFAULT_PRIORITY; + } + + return loglevel_map[map_index]; +} + namespace xamarin::android { void log_write (LogCategories category, LogLevel level, const char *message) noexcept { - size_t map_index = static_cast(level); - android_LogPriority priority; + __android_log_write (priority_for_level (level), CATEGORY_NAME (category), message); + } + + void + log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept + { + const char *safe_format = format == nullptr ? "" : format; + __android_log_vprint (priority_for_level (level), CATEGORY_NAME (category), safe_format, args); + } + + void + log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, level, format, args); + va_end (args); + } + + void + log_debugf (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; + } + + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Debug, format, args); + va_end (args); + } - if (map_index > loglevel_map_max_index) { - priority = DEFAULT_PRIORITY; - } else { - priority = loglevel_map[map_index]; + void + log_infof (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; } - __android_log_write (priority, CATEGORY_NAME (category), message); + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Info, format, args); + va_end (args); + } + + void + log_warnf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Warn, format, args); + va_end (args); + } + + void + log_errorf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Error, format, args); + va_end (args); } } diff --git a/src/native/mono/shared/log_types.hh b/src/native/mono/shared/log_types.hh index 0821ef59077..e476b82b1cd 100644 --- a/src/native/mono/shared/log_types.hh +++ b/src/native/mono/shared/log_types.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,6 +48,12 @@ namespace xamarin::android { // A slightly faster alternative to other log functions as it doesn't parse the message // for format placeholders nor it uses variable arguments void log_write (LogCategories category, LogLevel level, const char *message) noexcept; + void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; + void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept From 72cad5d01bf056aed289a38967678d0edb9f679c Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 14:07:39 +0200 Subject: [PATCH 09/12] Use printf configuration logging on MonoVM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../common/include/runtime-base/strings.hh | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/native/common/include/runtime-base/strings.hh b/src/native/common/include/runtime-base/strings.hh index 3d408811a37..2bf7bdbf203 100644 --- a/src/native/common/include/runtime-base/strings.hh +++ b/src/native/common/include/runtime-base/strings.hh @@ -11,9 +11,9 @@ #include #include -#if !defined(XA_HOST_MONOVM) -#include -#endif +namespace xamarin::android { + void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); +} #if defined(XA_HOST_MONOVM) #include @@ -208,11 +208,7 @@ namespace xamarin::android { } if (!can_access (start_index)) { -#if defined(XA_HOST_MONOVM) - log_error (LOG_DEFAULT, "Cannot convert string to integer, index {} is out of range", start_index); -#else log_errorf (LOG_DEFAULT, "Cannot convert string to integer, index %zu is out of range", start_index); -#endif return false; } @@ -236,9 +232,6 @@ namespace xamarin::android { } if (out_of_range || errno == ERANGE) { -#if defined(XA_HOST_MONOVM) - log_error (LOG_DEFAULT, "Value {} is out of range of this type ({}..{})", reinterpret_cast(s), static_cast(min), static_cast(max)); -#else log_errorf ( LOG_DEFAULT, "Value %s is out of range of this type (%lld..%llu)", @@ -246,25 +239,16 @@ namespace xamarin::android { static_cast(static_cast(min)), static_cast(static_cast(max)) ); -#endif return false; } if (endp == s) { -#if defined(XA_HOST_MONOVM) - log_error (LOG_DEFAULT, "Value {} does not represent a base {} integer", reinterpret_cast(s), base); -#else log_errorf (LOG_DEFAULT, "Value %s does not represent a base %d integer", reinterpret_cast(s), base); -#endif return false; } if (*endp != '\0') { -#if defined(XA_HOST_MONOVM) - log_error (LOG_DEFAULT, "Value {} has non-numeric characters at the end", reinterpret_cast(s)); -#else log_errorf (LOG_DEFAULT, "Value %s has non-numeric characters at the end", reinterpret_cast(s)); -#endif return false; } From 00aa7d4816efc291f5c1983e1f014d301ec88695 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 20 Jul 2026 16:03:32 -0500 Subject: [PATCH 10/12] Centralize native printf logging declarations Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0f0df1cb-536b-4c53-902e-cbbb9d8e9a15 --- src/native/clr/include/shared/log_types.hh | 14 +------------- .../common/include/runtime-base/strings.hh | 4 +--- .../common/include/shared/log_functions.hh | 18 ++++++++++++++++++ src/native/mono/shared/log_types.hh | 14 +------------- 4 files changed, 21 insertions(+), 29 deletions(-) create mode 100644 src/native/common/include/shared/log_functions.hh diff --git a/src/native/clr/include/shared/log_types.hh b/src/native/clr/include/shared/log_types.hh index b87347ad0a1..54e164c87f8 100644 --- a/src/native/clr/include/shared/log_types.hh +++ b/src/native/clr/include/shared/log_types.hh @@ -1,13 +1,11 @@ #pragma once -#include #include #include #include #include -#include "java-interop-logger.h" -#include +#include // We redeclare macros here #if defined(log_debug) @@ -45,16 +43,6 @@ #define log_fatal(_category_, _fmt_, ...) log_fatal_fmt ((_category_), (_fmt_) __VA_OPT__(,) __VA_ARGS__) namespace xamarin::android { - // A slightly faster alternative to other log functions as it doesn't parse the message - // for format placeholders nor it uses variable arguments - void log_write (LogCategories category, LogLevel level, const char *message) noexcept; - void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; - void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); - void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept { diff --git a/src/native/common/include/runtime-base/strings.hh b/src/native/common/include/runtime-base/strings.hh index 2bf7bdbf203..589e2f45653 100644 --- a/src/native/common/include/runtime-base/strings.hh +++ b/src/native/common/include/runtime-base/strings.hh @@ -11,9 +11,7 @@ #include #include -namespace xamarin::android { - void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); -} +#include #if defined(XA_HOST_MONOVM) #include diff --git a/src/native/common/include/shared/log_functions.hh b/src/native/common/include/shared/log_functions.hh new file mode 100644 index 00000000000..098b739a684 --- /dev/null +++ b/src/native/common/include/shared/log_functions.hh @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "java-interop-logger.h" +#include + +namespace xamarin::android { + // A slightly faster alternative to other log functions as it doesn't parse the message + // for format placeholders nor it uses variable arguments + void log_write (LogCategories category, LogLevel level, const char *message) noexcept; + void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; + void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); +} diff --git a/src/native/mono/shared/log_types.hh b/src/native/mono/shared/log_types.hh index e476b82b1cd..bbb0fb6e936 100644 --- a/src/native/mono/shared/log_types.hh +++ b/src/native/mono/shared/log_types.hh @@ -1,13 +1,11 @@ #pragma once -#include #include #include #include #include -#include "java-interop-logger.h" -#include +#include // We redeclare macros here #if defined(log_debug) @@ -45,16 +43,6 @@ #define log_fatal(_category_, _fmt_, ...) log_fatal_fmt ((_category_), (_fmt_) __VA_OPT__(,) __VA_ARGS__) namespace xamarin::android { - // A slightly faster alternative to other log functions as it doesn't parse the message - // for format placeholders nor it uses variable arguments - void log_write (LogCategories category, LogLevel level, const char *message) noexcept; - void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; - void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); - void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept { From fcd87d4544f416de4e0a25ca0da843868562cb36 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 20 Jul 2026 16:06:18 -0500 Subject: [PATCH 11/12] Reuse NativeAOT bridge status strings Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0f0df1cb-536b-4c53-902e-cbbb9d8e9a15 --- src/native/nativeaot/host/bridge-processing.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/native/nativeaot/host/bridge-processing.cc b/src/native/nativeaot/host/bridge-processing.cc index eb636515d6f..b3625794791 100644 --- a/src/native/nativeaot/host/bridge-processing.cc +++ b/src/native/nativeaot/host/bridge-processing.cc @@ -18,13 +18,16 @@ void BridgeProcessing::naot_initialize_on_runtime_init (JNIEnv *env) noexcept GCUserPeerable_jiAddManagedReference = env->GetMethodID (GCUserPeerable_class, "jiAddManagedReference", "(Ljava/lang/Object;)V"); GCUserPeerable_jiClearManagedReferences = env->GetMethodID (GCUserPeerable_class, "jiClearManagedReferences", "()V"); + constexpr char ABSENT[] = "absent"; + constexpr char PRESENT[] = "present"; + if (GCUserPeerable_jiAddManagedReference == nullptr || GCUserPeerable_jiClearManagedReferences == nullptr) [[unlikely]] { Helpers::abort_applicationf ( LOG_DEFAULT, std::source_location::current (), "Failed to find GCUserPeerable method(s): jiAddManagedReference (%s); jiClearManagedReferences (%s)", - GCUserPeerable_jiAddManagedReference == nullptr ? "absent" : "present", - GCUserPeerable_jiClearManagedReferences == nullptr ? "absent" : "present" + GCUserPeerable_jiAddManagedReference == nullptr ? ABSENT : PRESENT, + GCUserPeerable_jiClearManagedReferences == nullptr ? ABSENT : PRESENT ); } } From 71cfe50274642562287e32c2088845b1c7a6fda3 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 20 Jul 2026 16:48:28 -0500 Subject: [PATCH 12/12] Use canonical printf logging declarations Include the lightweight declaration header from both implementations and the printf-only abort helper so signatures remain checked without adding the std::format surface. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0f0df1cb-536b-4c53-902e-cbbb9d8e9a15 --- src/native/clr/shared/helpers.cc | 2 +- src/native/clr/shared/log_functions.cc | 3 +-- src/native/mono/shared/log_functions.cc | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/native/clr/shared/helpers.cc b/src/native/clr/shared/helpers.cc index 5c95342b1af..ac482e9ae74 100644 --- a/src/native/clr/shared/helpers.cc +++ b/src/native/clr/shared/helpers.cc @@ -4,7 +4,7 @@ #include #include -#include +#include using namespace xamarin::android; diff --git a/src/native/clr/shared/log_functions.cc b/src/native/clr/shared/log_functions.cc index 2f3db78abe7..ad6bca52539 100644 --- a/src/native/clr/shared/log_functions.cc +++ b/src/native/clr/shared/log_functions.cc @@ -5,9 +5,8 @@ #include -#include "java-interop-logger.h" -#include #include +#include using namespace xamarin::android; diff --git a/src/native/mono/shared/log_functions.cc b/src/native/mono/shared/log_functions.cc index 16e8eda7525..0fe808a922e 100644 --- a/src/native/mono/shared/log_functions.cc +++ b/src/native/mono/shared/log_functions.cc @@ -4,8 +4,7 @@ #include -#include "java-interop-logger.h" -#include +#include // Must match the same ordering as LogCategories static constexpr std::array log_names = {