From 55934e486702ad50f1973ea0113913e41ae328d4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 28 Aug 2026 00:18:58 +0200 Subject: [PATCH 1/6] [native] Replace the AndroidSystem path statics with a POD buffer `AndroidSystem` kept five of its members in `std::string`/`std::array`: `primary_override_dir`, `native_libraries_dir`, `app_code_cache_dir`, `single_app_lib_directory` and `override_dirs`. Because they are `inline static` with dynamic initialization, the compiler emits a guard variable *and* an `atexit` registration for them in **every** translation unit that includes `android-system.hh` - even in ones that never touch them. `logger.cc`, `internal-pinvokes-clr.cc`, `internal-pinvokes-shared.cc` and `android-system-shared.cc` each paid four libc++ references (`~basic_string`, `operator delete`, `__cxa_guard_acquire`, `__cxa_guard_release`) without using a single one of these directories. Replace them with `path_buffer`, a trivial aggregate holding an inline buffer plus an optional heap buffer. Being a POD, static instances are constant-initialized, so neither a guard variable nor an `atexit` registration is emitted. Paths that fit in `SENSIBLE_PATH_MAX` need no allocation at all and longer ones are moved to the heap, so - unlike the fixed `char[]` array NativeAOT used for `primary_override_dir` - there is no hard limit on the path length and no abort when it is exceeded. The directory arrays become plain `const char*` arrays whose entries are `malloc`ed, which also drops an `operator new[]` from the non-split-APK path. This lets `primary_override_dir` be shared by all three hosts, removing three `#if defined (XA_HOST_NATIVEAOT)` blocks and `determine_primary_override_dir()`. Undefined libc++ references in the CoreCLR archives: 58 -> 31. `libnet-android.release.so`: 539,464 -> 536,184 bytes (-3,280). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e --- src/native/clr/host/assembly-store.cc | 4 +- src/native/clr/host/fastdev-assemblies.cc | 18 ++-- src/native/clr/host/host.cc | 16 ++-- .../include/runtime-base/android-system.hh | 88 +++++++------------ .../clr/include/runtime-base/path-buffer.hh | 60 +++++++++++++ src/native/clr/runtime-base/android-system.cc | 49 +++++++---- 6 files changed, 146 insertions(+), 89 deletions(-) create mode 100644 src/native/clr/include/runtime-base/path-buffer.hh diff --git a/src/native/clr/host/assembly-store.cc b/src/native/clr/host/assembly-store.cc index d0ea1684845..eed53bc53cd 100644 --- a/src/native/clr/host/assembly-store.cc +++ b/src/native/clr/host/assembly-store.cc @@ -315,8 +315,8 @@ namespace { return; } - std::string const& code_cache_dir = AndroidSystem::get_app_code_cache_dir (); - if (code_cache_dir.empty ()) { + const char *code_cache_dir = AndroidSystem::get_app_code_cache_dir (); + if (*code_cache_dir == '\0') { return; } diff --git a/src/native/clr/host/fastdev-assemblies.cc b/src/native/clr/host/fastdev-assemblies.cc index bea8fecc272..b580f80171f 100644 --- a/src/native/clr/host/fastdev-assemblies.cc +++ b/src/native/clr/host/fastdev-assemblies.cc @@ -36,9 +36,9 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si return nullptr; } - std::string const& override_dir_path = AndroidSystem::get_primary_override_dir (); + const char *override_dir_path = AndroidSystem::get_primary_override_dir (); if (!Util::dir_exists (override_dir_path)) [[unlikely]] { - log_debugf (LOG_ASSEMBLY, "Override directory '%s' does not exist", override_dir_path.c_str ()); + log_debugf (LOG_ASSEMBLY, "Override directory '%s' does not exist", override_dir_path); return nullptr; } @@ -47,9 +47,9 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si if (override_dir_fd < 0) [[unlikely]] { pthread_mutex_lock (&override_dir_lock); if (override_dir_fd < 0) [[likely]] { - override_dir = opendir (override_dir_path.c_str ()); + override_dir = opendir (override_dir_path); if (override_dir == nullptr) [[unlikely]] { - log_warnf (LOG_ASSEMBLY, "Failed to open override dir '%s'. %s", override_dir_path.c_str (), strerror (errno)); + log_warnf (LOG_ASSEMBLY, "Failed to open override dir '%s'. %s", override_dir_path, strerror (errno)); pthread_mutex_unlock (&override_dir_lock); return nullptr; } @@ -62,7 +62,7 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si LOG_ASSEMBLY, "Attempting to load FastDev assembly '%.*s' from override directory '%s'", static_cast(name.length ()), name.data (), - override_dir_path.c_str () + override_dir_path ); if (!Util::file_exists (override_dir_fd, name)) { @@ -135,14 +135,14 @@ auto FastDevAssemblies::build_tpa_list (std::string &tpa_list) noexcept -> bool { tpa_list.clear (); - std::string const& override_dir_path = AndroidSystem::get_primary_override_dir (); + const char *override_dir_path = AndroidSystem::get_primary_override_dir (); if (!Util::dir_exists (override_dir_path)) { return false; } - DIR *dir = opendir (override_dir_path.c_str ()); + DIR *dir = opendir (override_dir_path); if (dir == nullptr) { - log_warnf (LOG_ASSEMBLY, "FastDev: failed to open override dir '%s'. %s", override_dir_path.c_str (), std::strerror (errno)); + log_warnf (LOG_ASSEMBLY, "FastDev: failed to open override dir '%s'. %s", override_dir_path, std::strerror (errno)); return false; } @@ -185,7 +185,7 @@ auto FastDevAssemblies::build_tpa_list (std::string &tpa_list) noexcept -> bool LOG_ASSEMBLY, "FastDev: built TPA list with %zu assemblies from '%s' (corelib=%s, r2r=%s)", count, - override_dir_path.c_str (), + override_dir_path, found_corelib ? "true" : "false", found_r2r ? "true" : "false" ); diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index e0abcbde019..053354531f8 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -91,16 +91,16 @@ bool Host::clr_external_assembly_probe (const char *path, void **data_start, int [[gnu::always_inline]] void Host::scan_filesystem_for_assemblies_and_libraries () noexcept { - std::string const& native_lib_dir = AndroidSystem::get_native_libraries_dir (); - log_debugf (LOG_ASSEMBLY, "Looking for assemblies in '%s'", native_lib_dir.c_str ()); + const char *native_lib_dir = AndroidSystem::get_native_libraries_dir (); + log_debugf (LOG_ASSEMBLY, "Looking for assemblies in '%s'", native_lib_dir); - DIR *lib_dir = opendir (native_lib_dir.c_str ()); + DIR *lib_dir = opendir (native_lib_dir); if (lib_dir == nullptr) [[unlikely]] { Helpers::abort_applicationf ( LOG_ASSEMBLY, std::source_location::current (), "Unable to open native library directory '%s'. %s", - native_lib_dir.c_str (), + native_lib_dir, std::strerror (errno) ); } @@ -111,7 +111,7 @@ void Host::scan_filesystem_for_assemblies_and_libraries () noexcept LOG_ASSEMBLY, std::source_location::current (), "Unable to obtain file descriptor for opened directory '%s'. %s", - native_lib_dir.c_str (), + native_lib_dir, std::strerror (errno) ); } @@ -121,7 +121,7 @@ void Host::scan_filesystem_for_assemblies_and_libraries () noexcept dirent *cur = readdir (lib_dir); if (cur == nullptr) { if (errno != 0) { - log_warnf (LOG_ASSEMBLY, "Failed to open a directory entry from '%s': %s", native_lib_dir.c_str (), std::strerror (errno)); + log_warnf (LOG_ASSEMBLY, "Failed to open a directory entry from '%s': %s", native_lib_dir, std::strerror (errno)); continue; // No harm, keep going } break; // we're done @@ -138,7 +138,7 @@ void Host::scan_filesystem_for_assemblies_and_libraries () noexcept continue; } - log_debugf (LOG_ASSEMBLY, "Found assembly store in '%s/%s'", native_lib_dir.c_str (), Constants::assembly_store_file_name.data ()); + log_debugf (LOG_ASSEMBLY, "Found assembly store in '%s/%s'", native_lib_dir, Constants::assembly_store_file_name.data ()); std::string store_path = native_lib_dir; store_path.append ("/"sv); @@ -338,7 +338,7 @@ void Host::Java_mono_android_Runtime_initInternal ( AndroidSystem::set_app_code_cache_dir (applicationDirs[Constants::APP_DIRS_CODE_CACHE_DIR_INDEX]); AndroidSystem::create_update_dir (AndroidSystem::get_primary_override_dir ()); AndroidSystem::setup_environment (); - Logger::init_reference_logging (AndroidSystem::get_primary_override_dir ().c_str ()); + Logger::init_reference_logging (AndroidSystem::get_primary_override_dir ()); jstring_array_wrapper runtimeApks (env, runtimeApksJava); AndroidSystem::setup_app_library_directories (runtimeApks, applicationDirs, haveSplitApks); diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index 134b7201385..987316d58e6 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -4,13 +4,13 @@ #include #include #include -#include #include #include "../constants.hh" #include #include "../runtime-base/cpu-arch.hh" #include +#include "path-buffer.hh" #include "util.hh" namespace xamarin::android { @@ -32,11 +32,11 @@ namespace xamarin::android { #if !defined (XA_HOST_NATIVEAOT) // This optimizes things a little bit. The array is allocated at build time, so we pay no cost for its // allocation and at run time it allows us to skip dynamic memory allocation. - inline static std::array single_app_lib_directory{}; - inline static std::span app_lib_directories; + inline static const char *single_app_lib_directory [1] { "" }; + inline static std::span app_lib_directories; // TODO: override dirs not implemented - inline static std::array override_dirs{}; + inline static const char *override_dirs [1] { "" }; static constexpr std::array android_abi_names { std::string_view { "unknown" }, // CPU_KIND_UNKNOWN @@ -73,32 +73,34 @@ namespace xamarin::android { running_in_emulator = yesno; } -#if defined (XA_HOST_NATIVEAOT) static auto get_primary_override_dir () noexcept -> const char* { - return primary_override_dir; + return primary_override_dir.get (); } -#else - static auto get_primary_override_dir () noexcept -> std::string const& - { - return primary_override_dir; - } -#endif static void set_primary_override_dir (jstring_wrapper& home) noexcept { -#if defined (XA_HOST_NATIVEAOT) - ssize_t result = format_primary_override_dir (home, primary_override_dir, sizeof (primary_override_dir)); - abort_unless (result >= 0, "Primary override directory path is too long"); -#else - primary_override_dir = determine_primary_override_dir (home); -#endif + char stack_buffer [Constants::SENSIBLE_PATH_MAX]; + char *path = stack_buffer; + ssize_t result = format_primary_override_dir (home, path, sizeof (stack_buffer)); + if (result < 0) { + size_t required_capacity = static_cast(-result); + path = static_cast (std::malloc (required_capacity)); + abort_unless (path != nullptr, "Failed to allocate primary override directory path"); + result = format_primary_override_dir (home, path, required_capacity); + } + abort_unless (result >= 0, "Failed to format primary override directory path using the required capacity"); + + primary_override_dir.assign (std::string_view { path, static_cast(result) }); + if (path != stack_buffer) { + std::free (path); + } } #if !defined (XA_HOST_NATIVEAOT) - static auto get_app_code_cache_dir () noexcept -> std::string const& + static auto get_app_code_cache_dir () noexcept -> const char* { - return app_code_cache_dir; + return app_code_cache_dir.get (); } static void set_app_code_cache_dir (jstring_wrapper& code_cache_dir) noexcept @@ -106,12 +108,12 @@ namespace xamarin::android { app_code_cache_dir.assign (code_cache_dir.get_cstr ()); } - static auto get_native_libraries_dir () noexcept -> std::string const& + static auto get_native_libraries_dir () noexcept -> const char* { - return native_libraries_dir; + return native_libraries_dir.get (); } - static void create_update_dir (std::string const& override_dir) noexcept + static void create_update_dir (const char *override_dir) noexcept { if constexpr (Constants::is_release_build) { /* @@ -127,8 +129,8 @@ namespace xamarin::android { } } - log_debugf (LOG_DEFAULT, "Creating public update directory: `%s`", override_dir.c_str ()); - Util::create_public_directory (override_dir.c_str ()); + log_debugf (LOG_DEFAULT, "Creating public update directory: `%s`", override_dir); + Util::create_public_directory (override_dir); } #endif @@ -152,9 +154,9 @@ namespace xamarin::android { static auto load_dso_from_any_directories (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*; private: - static auto format_full_dso_path (std::string const& base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t; + static auto format_full_dso_path (std::string_view const& base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t; - static auto get_full_dso_path (std::string const& base_dir, std::string_view const& dso_path, char *stack_buffer, size_t stack_buffer_size) noexcept -> char* + static auto get_full_dso_path (std::string_view const& base_dir, std::string_view const& dso_path, char *stack_buffer, size_t stack_buffer_size) noexcept -> char* { ssize_t result = format_full_dso_path (base_dir, dso_path, stack_buffer, stack_buffer_size); if (result >= 0) { @@ -213,38 +215,14 @@ namespace xamarin::android { return static_cast(length); } -#if !defined (XA_HOST_NATIVEAOT) - static auto determine_primary_override_dir (jstring_wrapper &home) noexcept -> std::string - { - char stack_buffer [Constants::SENSIBLE_PATH_MAX]; - char *name = stack_buffer; - ssize_t result = format_primary_override_dir (home, name, sizeof (stack_buffer)); - if (result < 0) { - size_t required_capacity = static_cast(-result); - name = static_cast (std::malloc (required_capacity)); - abort_unless (name != nullptr, "Failed to allocate primary override directory path"); - result = format_primary_override_dir (home, name, required_capacity); - } - abort_unless (result >= 0, "Failed to format primary override directory path using the required capacity"); - - std::string path { name, static_cast(result) }; - if (name != stack_buffer) { - std::free (name); - } - return path; - } -#endif - private: static inline long max_gref_count = 0; static inline bool running_in_emulator = false; static inline bool embedded_dso_mode_enabled = false; -#if defined (XA_HOST_NATIVEAOT) - static inline char primary_override_dir[Constants::SENSIBLE_PATH_MAX] {}; -#else - static inline std::string primary_override_dir; - static inline std::string native_libraries_dir; - static inline std::string app_code_cache_dir; + static inline path_buffer primary_override_dir {}; +#if !defined (XA_HOST_NATIVEAOT) + static inline path_buffer native_libraries_dir {}; + static inline path_buffer app_code_cache_dir {}; #if defined (DEBUG) static inline BundledProperty *bundled_properties = nullptr; diff --git a/src/native/clr/include/runtime-base/path-buffer.hh b/src/native/clr/include/runtime-base/path-buffer.hh new file mode 100644 index 00000000000..bbc730e66e1 --- /dev/null +++ b/src/native/clr/include/runtime-base/path-buffer.hh @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace xamarin::android { + // Storage for a path which is set once, early during startup, and then only read for as long as + // the process lives. Paths that fit in the inline buffer (the overwhelming majority of them) need + // no allocation at all, longer ones are moved to the heap, so there is no hard limit on the path + // length. + // + // The type is a trivial aggregate on purpose. Static instances of it are constant-initialized, + // which means that the compiler emits neither a guard variable nor an `atexit` registration for + // them - unlike for a `std::string`, which needs both in every translation unit that includes the + // declaration. The heap buffer is intentionally never released for the last assigned value, the + // instances are expected to live for as long as the process does. + template + struct path_buffer + { + static_assert (InlineCapacity > 0, "Inline capacity must not be zero"); + + char inline_buffer [InlineCapacity]; + char *heap_buffer; + + auto get () const noexcept -> const char* + { + return heap_buffer != nullptr ? heap_buffer : inline_buffer; + } + + void assign (std::string_view const& value) noexcept + { + // The previous heap buffer, if any, is released here. Values are assigned at most a + // handful of times, so there's no point in trying to reuse an allocation. + std::free (heap_buffer); + heap_buffer = nullptr; + + char *destination = inline_buffer; + if (value.length () >= InlineCapacity) { + size_t capacity = Helpers::add_with_overflow_check (value.length (), 1uz); + heap_buffer = static_cast (std::malloc (capacity)); + if (heap_buffer == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_DEFAULT, "Unable to allocate memory for a path"); + } + destination = heap_buffer; + } + + memcpy (destination, value.data (), value.length ()); + destination [value.length ()] = '\0'; + } + + void assign (const char *value) noexcept + { + assign (std::string_view { value != nullptr ? value : "" }); + } + }; +} diff --git a/src/native/clr/runtime-base/android-system.cc b/src/native/clr/runtime-base/android-system.cc index 9447df53b80..a4078deabfd 100644 --- a/src/native/clr/runtime-base/android-system.cc +++ b/src/native/clr/runtime-base/android-system.cc @@ -195,14 +195,26 @@ AndroidSystem::add_apk_libdir (std::string_view const& apk, size_t &index, std:: { abort_unless (index < app_lib_directories.size (), "Index out of range"); static constexpr std::string_view lib_prefix { "!/lib/" }; - std::string dir; - dir.reserve (apk.size () + lib_prefix.size () + abi.size ()); - dir.assign (apk); - dir.append (lib_prefix); - dir.append (abi); + size_t dir_length = Helpers::add_with_overflow_check (apk.length (), lib_prefix.length ()); + dir_length = Helpers::add_with_overflow_check (dir_length, abi.length ()); + + // The directory is used for as long as the process lives, it is never freed. + char *dir = static_cast (std::malloc (dir_length + 1uz)); + if (dir == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for an application library directory"); + } + + char *destination = dir; + memcpy (destination, apk.data (), apk.length ()); + destination += apk.length (); + memcpy (destination, lib_prefix.data (), lib_prefix.length ()); + destination += lib_prefix.length (); + memcpy (destination, abi.data (), abi.length ()); + dir [dir_length] = '\0'; + app_lib_directories [index] = dir; - log_debugf (LOG_ASSEMBLY, "Added APK DSO lookup location: %s", dir.c_str ()); + log_debugf (LOG_ASSEMBLY, "Added APK DSO lookup location: %s", dir); index++; } @@ -252,9 +264,12 @@ AndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks if (!is_embedded_dso_mode_enabled ()) { log_debugf (LOG_DEFAULT, "Setting up for DSO lookup in app data directories"); - app_lib_directories = std::span (single_app_lib_directory); - app_lib_directories [0] = std::string (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); - log_debugf (LOG_ASSEMBLY, "Added filesystem DSO lookup location: %s", app_lib_directories [0].c_str ()); + app_lib_directories = std::span (single_app_lib_directory); + app_lib_directories [0] = strdup (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); + if (app_lib_directories [0] == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for an application library directory"); + } + log_debugf (LOG_ASSEMBLY, "Added filesystem DSO lookup location: %s", app_lib_directories [0]); return; } @@ -262,10 +277,14 @@ AndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks if (have_split_apks) { // If split apks are used, then we will have just a single app library directory. Don't allocate any memory // dynamically in this case - AndroidSystem::app_lib_directories = std::span (single_app_lib_directory); + AndroidSystem::app_lib_directories = std::span (single_app_lib_directory); } else { size_t app_lib_directories_size = runtimeApks.get_length (); - AndroidSystem::app_lib_directories = std::span (new std::string[app_lib_directories_size], app_lib_directories_size); + auto directories = static_cast (std::malloc (app_lib_directories_size * sizeof (const char*))); + if (directories == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for the application library directories"); + } + AndroidSystem::app_lib_directories = std::span (directories, app_lib_directories_size); } uint16_t built_for_cpu = 0, running_on_cpu = 0; @@ -299,7 +318,7 @@ AndroidSystem::setup_environment () noexcept log_debugf (LOG_DEFAULT, "Loading environment from the override directory."); char stack_buffer [Util::LocalPathBufferSize]; - char *env_override_file = Util::join_paths (stack_buffer, sizeof (stack_buffer), primary_override_dir, Constants::OVERRIDE_ENVIRONMENT_FILE_NAME); + char *env_override_file = Util::join_paths (stack_buffer, sizeof (stack_buffer), get_primary_override_dir (), Constants::OVERRIDE_ENVIRONMENT_FILE_NAME); if (Util::file_exists (env_override_file)) { log_debugf (LOG_DEFAULT, "Loading %s", env_override_file); @@ -358,7 +377,7 @@ AndroidSystem::lookup_system_property (const char *name, size_t &value_len) noex ); } -auto AndroidSystem::format_full_dso_path (std::string const& base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t +auto AndroidSystem::format_full_dso_path (std::string_view const& base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t { bool is_rooted = Util::is_path_rooted (dso_path); bool add_lib_prefix = !base_dir.empty () && !is_rooted && !Util::path_has_directory_components (dso_path); @@ -394,9 +413,9 @@ auto AndroidSystem::load_dso_from_specified_dirs (TContainer directories, std::s return nullptr; } - for (std::string const& dir : directories) { + for (const char *dir : directories) { char stack_buffer [Util::LocalPathBufferSize]; - char *full_path = get_full_dso_path (dir, dso_name, stack_buffer, sizeof (stack_buffer)); + char *full_path = get_full_dso_path (std::string_view { dir }, dso_name, stack_buffer, sizeof (stack_buffer)); std::string_view full_path_view { full_path }; void *handle = DsoLoader::load (full_path_view, dl_flags, is_jni); From a96280ab9e3c9e4a1182d45cb2ca4a9b68a41d94 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 28 Aug 2026 07:55:38 +0200 Subject: [PATCH 2/6] [native] Drop path_buffer in favour of plain allocated strings The inline-buffer-plus-heap-fallback `path_buffer` was more machinery than these three values need. They are assigned exactly once, early during startup, and only read afterwards, so the inline buffer only ever saved a single `malloc` per value while costing 3 KB of `.bss`. Replace it with plain `const char*` members initialized to `""`. Pointers to a string literal are constant-initialized just like the aggregate was, so the guard variables and `atexit` registrations stay gone, which was the whole point of the change. The values are duplicated with a new `Util::duplicate_string()` helper, which aborts if the allocation fails. Also format the APK library directory with `snprintf` instead of open-coded `memcpy` calls - the exact length is computed up front, so the buffer is already known to be the right size. Undefined libc++ references are unchanged at 31. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e --- .../include/runtime-base/android-system.hh | 21 ++++--- .../clr/include/runtime-base/path-buffer.hh | 60 ------------------- src/native/clr/include/runtime-base/util.hh | 16 +++++ src/native/clr/runtime-base/android-system.cc | 26 ++++---- 4 files changed, 41 insertions(+), 82 deletions(-) delete mode 100644 src/native/clr/include/runtime-base/path-buffer.hh diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index 987316d58e6..2e6e218a2fc 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -10,7 +10,6 @@ #include #include "../runtime-base/cpu-arch.hh" #include -#include "path-buffer.hh" #include "util.hh" namespace xamarin::android { @@ -75,7 +74,7 @@ namespace xamarin::android { static auto get_primary_override_dir () noexcept -> const char* { - return primary_override_dir.get (); + return primary_override_dir; } static void set_primary_override_dir (jstring_wrapper& home) noexcept @@ -91,7 +90,7 @@ namespace xamarin::android { } abort_unless (result >= 0, "Failed to format primary override directory path using the required capacity"); - primary_override_dir.assign (std::string_view { path, static_cast(result) }); + primary_override_dir = Util::duplicate_string (std::string_view { path, static_cast(result) }); if (path != stack_buffer) { std::free (path); } @@ -100,17 +99,17 @@ namespace xamarin::android { #if !defined (XA_HOST_NATIVEAOT) static auto get_app_code_cache_dir () noexcept -> const char* { - return app_code_cache_dir.get (); + return app_code_cache_dir; } static void set_app_code_cache_dir (jstring_wrapper& code_cache_dir) noexcept { - app_code_cache_dir.assign (code_cache_dir.get_cstr ()); + app_code_cache_dir = Util::duplicate_string (code_cache_dir.get_cstr ()); } static auto get_native_libraries_dir () noexcept -> const char* { - return native_libraries_dir.get (); + return native_libraries_dir; } static void create_update_dir (const char *override_dir) noexcept @@ -219,10 +218,14 @@ namespace xamarin::android { static inline long max_gref_count = 0; static inline bool running_in_emulator = false; static inline bool embedded_dso_mode_enabled = false; - static inline path_buffer primary_override_dir {}; + // These are set once, early during startup, and are read for as long as the process lives. + // They are plain pointers so that they are constant-initialized: a `std::string` here would + // make the compiler emit a guard variable and an `atexit` registration in every translation + // unit which includes this header. + static inline const char *primary_override_dir = ""; #if !defined (XA_HOST_NATIVEAOT) - static inline path_buffer native_libraries_dir {}; - static inline path_buffer app_code_cache_dir {}; + static inline const char *native_libraries_dir = ""; + static inline const char *app_code_cache_dir = ""; #if defined (DEBUG) static inline BundledProperty *bundled_properties = nullptr; diff --git a/src/native/clr/include/runtime-base/path-buffer.hh b/src/native/clr/include/runtime-base/path-buffer.hh deleted file mode 100644 index bbc730e66e1..00000000000 --- a/src/native/clr/include/runtime-base/path-buffer.hh +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -namespace xamarin::android { - // Storage for a path which is set once, early during startup, and then only read for as long as - // the process lives. Paths that fit in the inline buffer (the overwhelming majority of them) need - // no allocation at all, longer ones are moved to the heap, so there is no hard limit on the path - // length. - // - // The type is a trivial aggregate on purpose. Static instances of it are constant-initialized, - // which means that the compiler emits neither a guard variable nor an `atexit` registration for - // them - unlike for a `std::string`, which needs both in every translation unit that includes the - // declaration. The heap buffer is intentionally never released for the last assigned value, the - // instances are expected to live for as long as the process does. - template - struct path_buffer - { - static_assert (InlineCapacity > 0, "Inline capacity must not be zero"); - - char inline_buffer [InlineCapacity]; - char *heap_buffer; - - auto get () const noexcept -> const char* - { - return heap_buffer != nullptr ? heap_buffer : inline_buffer; - } - - void assign (std::string_view const& value) noexcept - { - // The previous heap buffer, if any, is released here. Values are assigned at most a - // handful of times, so there's no point in trying to reuse an allocation. - std::free (heap_buffer); - heap_buffer = nullptr; - - char *destination = inline_buffer; - if (value.length () >= InlineCapacity) { - size_t capacity = Helpers::add_with_overflow_check (value.length (), 1uz); - heap_buffer = static_cast (std::malloc (capacity)); - if (heap_buffer == nullptr) [[unlikely]] { - Helpers::abort_application (LOG_DEFAULT, "Unable to allocate memory for a path"); - } - destination = heap_buffer; - } - - memcpy (destination, value.data (), value.length ()); - destination [value.length ()] = '\0'; - } - - void assign (const char *value) noexcept - { - assign (std::string_view { value != nullptr ? value : "" }); - } - }; -} diff --git a/src/native/clr/include/runtime-base/util.hh b/src/native/clr/include/runtime-base/util.hh index 531ba778850..ec1a57e1c1a 100644 --- a/src/native/clr/include/runtime-base/util.hh +++ b/src/native/clr/include/runtime-base/util.hh @@ -41,6 +41,22 @@ namespace xamarin::android { public: static constexpr size_t LocalPathBufferSize = Constants::SENSIBLE_PATH_MAX; + // Returns a copy of `str` allocated with `malloc`, aborting the application if the + // allocation fails. Used for values which are set once, early during startup, and which + // then live for as long as the process does - the copies are never freed. + static auto duplicate_string (std::string_view const& str) noexcept -> char* + { + size_t capacity = Helpers::add_with_overflow_check (str.length (), 1uz); + char *ret = static_cast (std::malloc (capacity)); + if (ret == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_DEFAULT, "Unable to allocate memory for a string copy"); + } + + memcpy (ret, str.data (), str.length ()); + ret [str.length ()] = '\0'; + return ret; + } + static int create_directory (const char *pathname, mode_t mode); static auto create_directory (std::string_view const& dir, mode_t mode) noexcept -> int diff --git a/src/native/clr/runtime-base/android-system.cc b/src/native/clr/runtime-base/android-system.cc index a4078deabfd..73d74785353 100644 --- a/src/native/clr/runtime-base/android-system.cc +++ b/src/native/clr/runtime-base/android-system.cc @@ -198,20 +198,23 @@ AndroidSystem::add_apk_libdir (std::string_view const& apk, size_t &index, std:: size_t dir_length = Helpers::add_with_overflow_check (apk.length (), lib_prefix.length ()); dir_length = Helpers::add_with_overflow_check (dir_length, abi.length ()); + size_t capacity = Helpers::add_with_overflow_check (dir_length, 1uz); // The directory is used for as long as the process lives, it is never freed. - char *dir = static_cast (std::malloc (dir_length + 1uz)); + char *dir = static_cast (std::malloc (capacity)); if (dir == nullptr) [[unlikely]] { Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for an application library directory"); } - char *destination = dir; - memcpy (destination, apk.data (), apk.length ()); - destination += apk.length (); - memcpy (destination, lib_prefix.data (), lib_prefix.length ()); - destination += lib_prefix.length (); - memcpy (destination, abi.data (), abi.length ()); - dir [dir_length] = '\0'; + int result = snprintf ( + dir, + capacity, + "%.*s%.*s%.*s", + static_cast(apk.length ()), apk.data (), + static_cast(lib_prefix.length ()), lib_prefix.data (), + static_cast(abi.length ()), abi.data () + ); + abort_unless (result >= 0 && static_cast(result) == dir_length, "Failed to format the application library directory path"); app_lib_directories [index] = dir; log_debugf (LOG_ASSEMBLY, "Added APK DSO lookup location: %s", dir); @@ -265,10 +268,7 @@ AndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks log_debugf (LOG_DEFAULT, "Setting up for DSO lookup in app data directories"); app_lib_directories = std::span (single_app_lib_directory); - app_lib_directories [0] = strdup (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); - if (app_lib_directories [0] == nullptr) [[unlikely]] { - Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for an application library directory"); - } + app_lib_directories [0] = Util::duplicate_string (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); log_debugf (LOG_ASSEMBLY, "Added filesystem DSO lookup location: %s", app_lib_directories [0]); return; } @@ -345,7 +345,7 @@ AndroidSystem::detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcep } else { log_debugf (LOG_ASSEMBLY, "Native libs extracted to %s, assuming application/android:extractNativeLibs == true", appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); set_embedded_dso_mode_enabled (false); - native_libraries_dir.assign (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); + native_libraries_dir = Util::duplicate_string (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); } if (libmonodroid_path != stack_buffer) { std::free (libmonodroid_path); From 3677aea0e8897910b45be4161870d3a42fca2826 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 28 Aug 2026 08:10:11 +0200 Subject: [PATCH 3/6] [native] Harden the app library directory array allocation Addresses review feedback: - `app_lib_directories_size * sizeof (const char*)` is now computed with `Helpers::multiply_with_overflow_check`. - A zero-length array is handled explicitly. `malloc (0)` may legitimately return `nullptr`, which the previous code would have misreported as an allocation failure; `setup_apk_directories ()` already aborts with a more accurate message when no directory ends up being added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e --- src/native/clr/runtime-base/android-system.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/native/clr/runtime-base/android-system.cc b/src/native/clr/runtime-base/android-system.cc index 73d74785353..7dfbc099e89 100644 --- a/src/native/clr/runtime-base/android-system.cc +++ b/src/native/clr/runtime-base/android-system.cc @@ -280,11 +280,19 @@ AndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks AndroidSystem::app_lib_directories = std::span (single_app_lib_directory); } else { size_t app_lib_directories_size = runtimeApks.get_length (); - auto directories = static_cast (std::malloc (app_lib_directories_size * sizeof (const char*))); - if (directories == nullptr) [[unlikely]] { - Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for the application library directories"); + if (app_lib_directories_size == 0uz) [[unlikely]] { + // `malloc (0)` is allowed to return `nullptr`, which we would misreport as an allocation + // failure. There is nothing to allocate anyway - `setup_apk_directories ()` below aborts + // with a more accurate message when no directory ends up being added. + AndroidSystem::app_lib_directories = std::span (); + } else { + size_t alloc_size = Helpers::multiply_with_overflow_check (app_lib_directories_size, sizeof (const char*)); + auto directories = static_cast (std::malloc (alloc_size)); + if (directories == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_ASSEMBLY, "Unable to allocate memory for the application library directories"); + } + AndroidSystem::app_lib_directories = std::span (directories, app_lib_directories_size); } - AndroidSystem::app_lib_directories = std::span (directories, app_lib_directories_size); } uint16_t built_for_cpu = 0, running_on_cpu = 0; From 6e84342a7e30b7a16d0bbd07c1a19578d1d9a05e Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 28 Aug 2026 13:37:56 +0200 Subject: [PATCH 4/6] [native] Remove jstring_wrapper::get_string_view () The view returned by `get_string_view ()` pointed at the UTF characters owned by the wrapper, so it dangled as soon as the wrapper released them. Nothing relied on the view being a view: two of the three callers immediately passed it to a path helper, and the third only needed a suffix comparison. Return the C string instead and let the callers build a view when they need one. `setup_apk_directories ()` used `std::string_view::ends_with ()`, so add a `Util::ends_with ()` that works on plain C strings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e --- src/native/clr/include/host/host-environment.hh | 2 +- src/native/clr/include/runtime-base/util.hh | 16 ++++++++++++++++ src/native/clr/runtime-base/android-system.cc | 16 ++++++++-------- .../common/include/runtime-base/jni-wrappers.hh | 11 ----------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/native/clr/include/host/host-environment.hh b/src/native/clr/include/host/host-environment.hh index 137beb468bd..9f16546dd32 100644 --- a/src/native/clr/include/host/host-environment.hh +++ b/src/native/clr/include/host/host-environment.hh @@ -91,7 +91,7 @@ namespace xamarin::android { [[gnu::flatten, gnu::always_inline]] static void create_xdg_directory (jstring_wrapper &home, std::string_view const& relative_path, std::string_view const& environment_variable_name) noexcept { - std::string_view home_path = home.get_string_view (); + const char *home_path = home.get_cstr (); char stack_buffer [Util::LocalPathBufferSize]; ssize_t result = Util::format_joined_path (stack_buffer, sizeof (stack_buffer), home_path, relative_path); abort_unless (result >= 0, "XDG directory path is too long"); diff --git a/src/native/clr/include/runtime-base/util.hh b/src/native/clr/include/runtime-base/util.hh index ec1a57e1c1a..71bd0dd3da9 100644 --- a/src/native/clr/include/runtime-base/util.hh +++ b/src/native/clr/include/runtime-base/util.hh @@ -326,6 +326,22 @@ namespace xamarin::android { return !path.empty () && path.contains ('/'); } + [[gnu::flatten, gnu::always_inline]] + static auto ends_with (const char *value, const char *suffix) noexcept -> bool + { + if (value == nullptr || suffix == nullptr) { + return false; + } + + size_t value_length = strlen (value); + size_t suffix_length = strlen (suffix); + if (suffix_length > value_length) { + return false; + } + + return memcmp (value + value_length - suffix_length, suffix, suffix_length) == 0; + } + // Returns the path length excluding NUL, or the negative required capacity including NUL. static auto format_joined_path (char *buffer, size_t buffer_size, std::string_view first, std::string_view second) noexcept -> ssize_t { diff --git a/src/native/clr/runtime-base/android-system.cc b/src/native/clr/runtime-base/android-system.cc index 7dfbc099e89..03d15b4b729 100644 --- a/src/native/clr/runtime-base/android-system.cc +++ b/src/native/clr/runtime-base/android-system.cc @@ -228,16 +228,16 @@ AndroidSystem::setup_apk_directories (unsigned short running_on_cpu, jstring_arr std::string_view const& abi = android_abi_names [running_on_cpu]; size_t number_of_added_directories = 0uz; - std::string_view base_apk{}; + const char *base_apk = nullptr; for (size_t i = 0uz; i < runtimeApks.get_length (); ++i) { jstring_wrapper &e = runtimeApks [i]; - std::string_view apk = e.get_string_view (); + const char *apk = e.get_cstr (); if (have_split_apks) { - if (apk.ends_with (Constants::split_config_abi_apk_name.data ())) { + if (Util::ends_with (apk, Constants::split_config_abi_apk_name.data ())) { add_apk_libdir (apk, number_of_added_directories, abi); break; - } else if (base_apk.empty () && apk.ends_with (Constants::base_apk_name)) { + } else if (base_apk == nullptr && Util::ends_with (apk, Constants::base_apk_name.data ())) { base_apk = apk; } } else { @@ -248,7 +248,7 @@ AndroidSystem::setup_apk_directories (unsigned short running_on_cpu, jstring_arr // This apparently can happen now... It seems that sometimes (when and why? No idea) when AAB format is used, bundletool // won't put the native libraries in a separate split config file, but it will instead put **all** of the ABIs // in base.apk - if (have_split_apks && number_of_added_directories == 0 && !base_apk.empty ()) { + if (have_split_apks && number_of_added_directories == 0 && base_apk != nullptr) { add_apk_libdir (base_apk, number_of_added_directories, abi); } @@ -342,7 +342,7 @@ void AndroidSystem::detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcept { // appDirs[Constants::APP_DIRS_DATA_DIR_INDEX] points to the native library directory - std::string_view app_data_dir = appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_string_view (); + const char *app_data_dir = appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr (); char stack_buffer [Util::LocalPathBufferSize]; char *libmonodroid_path = Util::join_paths (stack_buffer, sizeof (stack_buffer), app_data_dir, "libmonodroid.so"sv); @@ -351,9 +351,9 @@ AndroidSystem::detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcep log_debugf (LOG_ASSEMBLY, "%s not found, assuming application/android:extractNativeLibs == false", libmonodroid_path); set_embedded_dso_mode_enabled (true); } else { - log_debugf (LOG_ASSEMBLY, "Native libs extracted to %s, assuming application/android:extractNativeLibs == true", appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); + log_debugf (LOG_ASSEMBLY, "Native libs extracted to %s, assuming application/android:extractNativeLibs == true", app_data_dir); set_embedded_dso_mode_enabled (false); - native_libraries_dir = Util::duplicate_string (appDirs[Constants::APP_DIRS_DATA_DIR_INDEX].get_cstr ()); + native_libraries_dir = Util::duplicate_string (app_data_dir); } if (libmonodroid_path != stack_buffer) { std::free (libmonodroid_path); diff --git a/src/native/common/include/runtime-base/jni-wrappers.hh b/src/native/common/include/runtime-base/jni-wrappers.hh index 679aed3df59..baa2f082433 100644 --- a/src/native/common/include/runtime-base/jni-wrappers.hh +++ b/src/native/common/include/runtime-base/jni-wrappers.hh @@ -70,17 +70,6 @@ namespace xamarin::android return cstr; } - [[gnu::always_inline]] - const std::string_view get_string_view () noexcept - { - if (jstr == nullptr) { - return {}; - } - - ensure_cstr (); - return {cstr}; - } - jstring_wrapper& operator= (const jobject new_jo) noexcept { assign (reinterpret_cast (new_jo)); From 8df1354b8b44294bfa1259a2023c72781e9e13a3 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 28 Aug 2026 13:43:42 +0200 Subject: [PATCH 5/6] [native] Implement duplicate_string () on top of strdup () The hand-written copy existed to support a caller that passed a pointer and a length rather than a C string, but that caller formats its buffer with `snprintf ()` and only reaches the call when the result fits, so the buffer is already NUL terminated. With every caller passing a C string there is nothing left for `std::string_view` to do and the copy is just `strdup ()`. Keep the wrapper rather than calling `strdup ()` directly: it aborts on allocation failure, which saves each of the four callers from checking for null. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e --- src/native/clr/include/runtime-base/android-system.hh | 2 +- src/native/clr/include/runtime-base/util.hh | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index 2e6e218a2fc..09a38eaaf52 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -90,7 +90,7 @@ namespace xamarin::android { } abort_unless (result >= 0, "Failed to format primary override directory path using the required capacity"); - primary_override_dir = Util::duplicate_string (std::string_view { path, static_cast(result) }); + primary_override_dir = Util::duplicate_string (path); if (path != stack_buffer) { std::free (path); } diff --git a/src/native/clr/include/runtime-base/util.hh b/src/native/clr/include/runtime-base/util.hh index 71bd0dd3da9..78f57dd75d3 100644 --- a/src/native/clr/include/runtime-base/util.hh +++ b/src/native/clr/include/runtime-base/util.hh @@ -44,16 +44,13 @@ namespace xamarin::android { // Returns a copy of `str` allocated with `malloc`, aborting the application if the // allocation fails. Used for values which are set once, early during startup, and which // then live for as long as the process does - the copies are never freed. - static auto duplicate_string (std::string_view const& str) noexcept -> char* + static auto duplicate_string (const char *str) noexcept -> char* { - size_t capacity = Helpers::add_with_overflow_check (str.length (), 1uz); - char *ret = static_cast (std::malloc (capacity)); + char *ret = strdup (str); if (ret == nullptr) [[unlikely]] { Helpers::abort_application (LOG_DEFAULT, "Unable to allocate memory for a string copy"); } - memcpy (ret, str.data (), str.length ()); - ret [str.length ()] = '\0'; return ret; } From d4409ce792173922b55badecd578e6ca3a3125d7 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 28 Aug 2026 13:49:28 +0200 Subject: [PATCH 6/6] [native] Take the DSO base directory as a C string The only caller of `get_full_dso_path ()` iterates over a container of `const char*` directories and wrapped each one in a `std::string_view` purely to satisfy the signature. Take a C string instead and measure it once inside `format_full_dso_path ()`. `dso_path` stays a view: it originates in the DSO cache lookup, which compares name mutations built with `substr ()`, so a view is the right type there. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e --- .../clr/include/runtime-base/android-system.hh | 4 ++-- src/native/clr/runtime-base/android-system.cc | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index 09a38eaaf52..8a698069e4e 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -153,9 +153,9 @@ namespace xamarin::android { static auto load_dso_from_any_directories (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*; private: - static auto format_full_dso_path (std::string_view const& base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t; + static auto format_full_dso_path (const char *base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t; - static auto get_full_dso_path (std::string_view const& base_dir, std::string_view const& dso_path, char *stack_buffer, size_t stack_buffer_size) noexcept -> char* + static auto get_full_dso_path (const char *base_dir, std::string_view const& dso_path, char *stack_buffer, size_t stack_buffer_size) noexcept -> char* { ssize_t result = format_full_dso_path (base_dir, dso_path, stack_buffer, stack_buffer_size); if (result >= 0) { diff --git a/src/native/clr/runtime-base/android-system.cc b/src/native/clr/runtime-base/android-system.cc index 03d15b4b729..53c63e3ea28 100644 --- a/src/native/clr/runtime-base/android-system.cc +++ b/src/native/clr/runtime-base/android-system.cc @@ -385,14 +385,16 @@ AndroidSystem::lookup_system_property (const char *name, size_t &value_len) noex ); } -auto AndroidSystem::format_full_dso_path (std::string_view const& base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t +auto AndroidSystem::format_full_dso_path (const char *base_dir, std::string_view const& dso_path, char *buffer, size_t buffer_size) noexcept -> ssize_t { bool is_rooted = Util::is_path_rooted (dso_path); - bool add_lib_prefix = !base_dir.empty () && !is_rooted && !Util::path_has_directory_components (dso_path); + size_t base_dir_length = base_dir == nullptr ? 0uz : strlen (base_dir); + bool prepend_base_dir = base_dir_length > 0 && !is_rooted; + bool add_lib_prefix = prepend_base_dir && !Util::path_has_directory_components (dso_path); size_t dso_name_length = Util::get_dso_name_length (dso_path, add_lib_prefix); size_t path_length = dso_name_length; - if (!base_dir.empty () && !is_rooted) { - path_length = Helpers::add_with_overflow_check (base_dir.length (), dso_name_length); + if (prepend_base_dir) { + path_length = Helpers::add_with_overflow_check (base_dir_length, dso_name_length); path_length = Helpers::add_with_overflow_check (path_length, 1uz); } @@ -403,9 +405,9 @@ auto AndroidSystem::format_full_dso_path (std::string_view const& base_dir, std: } char *destination = buffer; - if (!base_dir.empty () && !is_rooted) { - memcpy (destination, base_dir.data (), base_dir.length ()); - destination += base_dir.length (); + if (prepend_base_dir) { + memcpy (destination, base_dir, base_dir_length); + destination += base_dir_length; *destination++ = Constants::DIR_SEP [0]; } @@ -423,7 +425,7 @@ auto AndroidSystem::load_dso_from_specified_dirs (TContainer directories, std::s for (const char *dir : directories) { char stack_buffer [Util::LocalPathBufferSize]; - char *full_path = get_full_dso_path (std::string_view { dir }, dso_name, stack_buffer, sizeof (stack_buffer)); + char *full_path = get_full_dso_path (dir, dso_name, stack_buffer, sizeof (stack_buffer)); std::string_view full_path_view { full_path }; void *handle = DsoLoader::load (full_path_view, dl_flags, is_jni);