Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
64f53d1
Replace simple CLR local strings
simonrozsival Aug 25, 2026
fa4f4f9
Remove CLR dynamic property strings
simonrozsival Aug 25, 2026
72b86d4
Narrow the logger strings include
simonrozsival Aug 25, 2026
962c572
Preserve unbounded generated names
simonrozsival Aug 25, 2026
23ac7c9
Use C allocation for generated names
simonrozsival Aug 25, 2026
48a37eb
Allocate override paths to their exact size
simonrozsival Aug 25, 2026
56b79d1
Keep short generated names on the stack
simonrozsival Aug 25, 2026
931f293
Retry formatting oversized CLR strings
simonrozsival Aug 25, 2026
21a8325
Use shared CLR buffer cleanup
simonrozsival Aug 25, 2026
833f4aa
Use free directly for CLR buffers
simonrozsival Aug 25, 2026
6ee0f1d
Use CLR pointers for buffer ownership
simonrozsival Aug 25, 2026
a7e70fe
Restore includes that strings.hh used to provide
simonrozsival Aug 26, 2026
114f986
Include strings.hh directly in util.cc
simonrozsival Aug 27, 2026
72053e0
Return a string_view from monodroid_get_system_property
simonrozsival Aug 27, 2026
69883f6
[native] Return a NUL-terminated string from monodroid_get_system_pro…
simonrozsival Aug 28, 2026
26e1235
[native] Address the review feedback on the property and typemap changes
simonrozsival Aug 28, 2026
4f80d36
[native] Parse the logging categories without std::string_view
simonrozsival Aug 28, 2026
2d30854
[native] Document that monodroid_get_system_property never allocates
simonrozsival Aug 28, 2026
e265b33
[native] Take file system paths as C strings in Util and Logger
simonrozsival Aug 28, 2026
1496159
[native] Fix bundled system property lookup and drop dead property code
simonrozsival Aug 28, 2026
0f60bd6
[native] Return the value, not the name, from Mono's bundled property…
simonrozsival Aug 28, 2026
db24706
[native] Report empty bundled properties as unset
simonrozsival Aug 28, 2026
eb95617
[native] Address self-review findings
simonrozsival Aug 28, 2026
11547a4
[native] Correct the documented lifetime of bundled property values
simonrozsival Aug 28, 2026
c4768da
[native] Fix two latent defects found while self-reviewing
simonrozsival Aug 28, 2026
f8ca254
[native] Consolidate the stack-buffer-with-heap-fallback retry
simonrozsival Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/native/clr/host/assembly-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,12 @@ namespace {
return;
}
{
dynamic_local_property_string prop_value;
if (AndroidSystem::monodroid_get_system_property ("debug.net.asmcache"sv, prop_value) > 0 && prop_value.get () != nullptr) {
if (prop_value.get ()[0] == '0') {
char prop_value[Constants::PROPERTY_VALUE_BUFFER_LEN];
const char *cache_prop = AndroidSystem::monodroid_get_system_property ("debug.net.asmcache", prop_value, sizeof (prop_value));
if (cache_prop != nullptr) {
if (cache_prop [0] == '0') {
cache_requested = false;
} else if (prop_value.get ()[0] == '1') {
} else if (cache_prop [0] == '1') {
cache_requested = true;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/native/clr/host/bridge-processing.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <cinttypes>
#include <cstdlib>
#include <unistd.h>

#include <host/bridge-processing.hh>
#include <host/host-common.hh>
Expand Down
2 changes: 1 addition & 1 deletion src/native/clr/host/host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,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 ());
Logger::init_reference_logging (AndroidSystem::get_primary_override_dir ().c_str ());

jstring_array_wrapper runtimeApks (env, runtimeApksJava);
AndroidSystem::setup_app_library_directories (runtimeApks, applicationDirs, haveSplitApks);
Expand Down
41 changes: 35 additions & 6 deletions src/native/clr/host/typemap.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <limits>

#include <host/typemap.hh>
#include <runtime-base/crc32.hh>
Expand All @@ -18,6 +21,24 @@ namespace {
return value_length == key_length && strncmp (value, key, key_length) == 0;
}

#if defined (DEBUG)
// Returns the length of the formatted name, or the negative capacity the caller must provide
// when `buffer` is too small.
auto format_managed_type_name (const char *type_name, const char *assembly_name, char *buffer, size_t buffer_size) noexcept -> ssize_t
{
int full_name_length = snprintf (buffer, buffer_size, "%s, %s", type_name, assembly_name);
abort_unless (full_name_length >= 0, "Failed to format the managed type name");

size_t length = static_cast<size_t>(full_name_length);
abort_unless (length < static_cast<size_t>(std::numeric_limits<ssize_t>::max ()), "Managed type name is too long");
if (length >= buffer_size) {
return -static_cast<ssize_t>(length + 1uz);
}

return static_cast<ssize_t>(length);
}
#endif // def DEBUG

class MonoGuidString
{
static inline constexpr size_t MVID_SIZE = 16;
Expand Down Expand Up @@ -152,14 +173,22 @@ auto TypeMapper::index_to_name (ssize_t idx, const char* typeName, const TypeMap
[[gnu::always_inline, gnu::flatten]]
auto TypeMapper::managed_to_java_debug (const char *typeName, const char *assemblyFullName) noexcept -> const char*
{
dynamic_local_path_string full_type_name;
full_type_name.append (typeName);
full_type_name.append (", "sv);
full_type_name.append (assemblyFullName);
char stack_buffer [Constants::SENSIBLE_PATH_MAX];
char *full_type_name = Util::format_with_retry (
stack_buffer,
sizeof (stack_buffer),
[typeName, assemblyFullName](char *buffer, size_t buffer_size) noexcept {
return format_managed_type_name (typeName, assemblyFullName, buffer, buffer_size);
}
);

ssize_t idx = find_index_by_hash (full_type_name.get (), type_map.managed_to_java, type_map_managed_type_names, MANAGED, JAVA);
ssize_t idx = find_index_by_hash (full_type_name, type_map.managed_to_java, type_map_managed_type_names, MANAGED, JAVA);
const char *mapped_name = index_to_name (idx, full_type_name, type_map.managed_to_java, type_map_java_type_names, MANAGED, JAVA);
if (full_type_name != stack_buffer) {
std::free (full_type_name);
}

return index_to_name (idx, full_type_name.get (), type_map.managed_to_java, type_map_java_type_names, MANAGED, JAVA);
return mapped_name;
}
#endif // def DEBUG

Expand Down
5 changes: 3 additions & 2 deletions src/native/clr/include/host/host-environment.hh
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ namespace xamarin::android {
}

[[gnu::flatten, gnu::always_inline]]
static auto lookup_system_property (std::string_view const& name, size_t &value_len,
static auto lookup_system_property (const char *name, size_t &value_len,
uint32_t const count, AppEnvironmentVariable const (&entries)[],
const char (&contents)[]) noexcept -> const char*
{
value_len = 0;
if (count == 0) {
return nullptr;
}

for (size_t i = 0; i < count; i++) {
AppEnvironmentVariable const& sys_prop = entries[i];
const char *prop_name = &contents[sys_prop.name_index];
if (name.compare (prop_name) != 0) {
if (strcmp (name, prop_name) != 0) {
continue;
}

Expand Down
2 changes: 2 additions & 0 deletions src/native/clr/include/host/os-bridge.hh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <jni.h>

#include <shared/cpp-util.hh>

#include "../runtime-base/logger.hh"

namespace xamarin::android {
Expand Down
111 changes: 65 additions & 46 deletions src/native/clr/include/runtime-base/android-system.hh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include <array>
#include <cstring>
#include <cstdio>
#include <limits>
#include <span>
#include <string>
Expand Down Expand Up @@ -79,7 +79,8 @@ namespace xamarin::android {
static void set_primary_override_dir (jstring_wrapper& home) noexcept
{
#if defined (XA_HOST_NATIVEAOT)
determine_primary_override_dir (home, primary_override_dir, sizeof (primary_override_dir));
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
Expand Down Expand Up @@ -111,14 +112,14 @@ namespace xamarin::android {
* However, if any logging is enabled (which should _not_ happen with
* pre-loaded apps!), we need the .__override__ directory...
*/
dynamic_local_property_string value;
if (log_categories == 0 && monodroid_get_system_property (Constants::DEBUG_MONO_PROFILE_PROPERTY, value) == 0) [[likely]] {
char value[Constants::PROPERTY_VALUE_BUFFER_LEN];
if (log_categories == 0 && monodroid_get_system_property (Constants::DEBUG_MONO_PROFILE_PROPERTY.data (), value, sizeof (value)) == nullptr) [[likely]] {
return;
}
}

log_debug (LOG_DEFAULT, "Creating public update directory: `{}`", override_dir);
Util::create_public_directory (override_dir);
Util::create_public_directory (override_dir.c_str ());
}
#endif

Expand All @@ -127,26 +128,19 @@ namespace xamarin::android {
return embedded_dso_mode_enabled;
}

static auto monodroid_get_system_property (std::string_view const& name, dynamic_local_property_string &value) noexcept -> int;

template<size_t Size>
static auto monodroid_get_system_property (std::string_view const& name, char (&value)[Size]) noexcept -> int
{
dynamic_local_property_string property_value;
int result = monodroid_get_system_property (name, property_value);
if (result > 0) {
if (property_value.length () >= Size) {
value [0] = '\0';
return -1;
}
memcpy (value, property_value.get (), property_value.length ());
value [property_value.length ()] = '\0';
} else {
value [0] = '\0';
}
return result;
}

// Returns the property's NUL-terminated value, or `nullptr` if it is not set. A property
// that is set to an empty value is reported as not set, matching `__system_property_get`,
// which cannot tell the two apart.
//
// `value` is a scratch buffer of at least `Constants::PROPERTY_VALUE_BUFFER_LEN` bytes, used
// to receive Android system properties. Bundled properties are returned without copying, so
// their length is not limited by `value_size`.
//
// Nothing is ever allocated and the caller must not free the result: it is either `value` or
// a pointer to bundled property data. The latter is only guaranteed to stay valid until the
// next `setup_environment()` call, because in Debug builds bundled properties are stored in
// a mutable map. Copy the value if you need to retain it.
static auto monodroid_get_system_property (const char *name, char *value, size_t value_size) noexcept -> const char*;
static void detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcept;
static void setup_environment () noexcept;
static void setup_app_library_directories (jstring_array_wrapper& runtimeApks, jstring_array_wrapper& appDirs, bool have_split_apks) noexcept;
Expand All @@ -159,8 +153,8 @@ namespace xamarin::android {
static auto load_dso_from_specified_dirs (TContainer directories, std::string_view const& dso_name, int dl_flags, bool is_jni) noexcept -> void*;
static auto load_dso_from_app_lib_dirs (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*;
static auto load_dso_from_override_dirs (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*;
static auto lookup_system_property (std::string_view const &name, size_t &value_len) noexcept -> const char*;
static auto monodroid__system_property_get (std::string_view const&, char *sp_value, size_t sp_value_len) noexcept -> int;
static auto lookup_system_property (const char *name, size_t &value_len) noexcept -> const char*;
static auto monodroid__system_property_get (const char *name, char *sp_value) noexcept -> int;
static auto get_max_gref_count_from_system () noexcept -> long;
static void add_apk_libdir (std::string_view const& apk, size_t &index, std::string_view const& abi) noexcept;
static void setup_apk_directories (unsigned short running_on_cpu, jstring_array_wrapper &runtimeApks, bool have_split_apks) noexcept;
Expand All @@ -175,28 +169,53 @@ namespace xamarin::android {
embedded_dso_mode_enabled = yesno;
}

#if defined (XA_HOST_NATIVEAOT)
static void determine_primary_override_dir (jstring_wrapper &home, char *buffer, size_t buffer_size) noexcept
{
dynamic_local_string<SENSIBLE_PATH_MAX> name { home.get_cstr () };
name.append ("/")
.append (Constants::OVERRIDE_DIRECTORY_NAME)
.append ("/")
.append (Constants::android_lib_abi);

abort_unless (name.length () < buffer_size, "Primary override directory path is too long");
memcpy (buffer, name.get (), name.length () + 1);
static auto format_primary_override_dir (jstring_wrapper &home, char *buffer, size_t buffer_size) noexcept -> ssize_t
{
abort_unless (buffer != nullptr, "Primary override directory buffer must not be null");

// `jstring_wrapper::get_cstr()` returns `nullptr` for a null `jstring`, and passing that
// to `%s` is undefined behaviour. An app without a files directory cannot work anyway.
const char *home_path = home.get_cstr ();
abort_unless (home_path != nullptr, "Application home directory must not be null");

int length = snprintf (
buffer,
buffer_size,
"%s/%.*s/%.*s",
home_path,
static_cast<int>(Constants::OVERRIDE_DIRECTORY_NAME.length ()),
Constants::OVERRIDE_DIRECTORY_NAME.data (),
static_cast<int>(Constants::android_lib_abi.length ()),
Constants::android_lib_abi.data ()
);
abort_unless (length >= 0, "Failed to format primary override directory path");
size_t required_capacity = Helpers::add_with_overflow_check<size_t> (static_cast<size_t>(length), 1uz);
abort_unless (required_capacity <= static_cast<size_t>(std::numeric_limits<ssize_t>::max ()), "Primary override directory path is too long");
if (buffer_size < required_capacity) {
return -static_cast<ssize_t>(required_capacity);
}
return static_cast<ssize_t>(length);
}
#else

#if !defined (XA_HOST_NATIVEAOT)
static auto determine_primary_override_dir (jstring_wrapper &home) noexcept -> std::string
{
dynamic_local_string<SENSIBLE_PATH_MAX> name { home.get_cstr () };
name.append ("/")
.append (Constants::OVERRIDE_DIRECTORY_NAME)
.append ("/")
.append (Constants::android_lib_abi);

return {name.get (), name.length ()};
char stack_buffer [Constants::SENSIBLE_PATH_MAX];
size_t length;
char *name = Util::format_with_retry (
stack_buffer,
sizeof (stack_buffer),
[&home](char *buffer, size_t buffer_size) noexcept {
return format_primary_override_dir (home, buffer, buffer_size);
},
&length
);

std::string path { name, length };
if (name != stack_buffer) {
std::free (name);
}
return path;
}
#endif

Expand Down
11 changes: 4 additions & 7 deletions src/native/clr/include/runtime-base/logger.hh
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@

#include <cstdio>

#include <string_view>

#include <shared/log_types.hh>
#include <runtime-base/strings.hh>

namespace xamarin::android {
class Logger
{
public:
static void init_logging_categories () noexcept;
static void init_reference_logging (std::string_view const& override_dir) noexcept;
static void init_reference_logging (const char *override_dir) noexcept;

static auto log_timing_categories () noexcept -> LogTimingCategories
{
Expand Down Expand Up @@ -50,11 +47,11 @@ namespace xamarin::android {
}

private:
static auto open_file (std::string_view const& path) noexcept -> FILE*;
static auto open_file (LogCategories category, std::string_view const& custom_path, std::string_view const& override_dir, std::string_view const& fallback_filename) noexcept -> FILE*;
static auto open_file (const char *path) noexcept -> FILE*;
static auto open_file (LogCategories category, const char *custom_path, const char *override_dir, const char *fallback_filename) noexcept -> FILE*;

private:
static bool set_category (std::string_view const& name, string_segment& arg, unsigned int entry, bool arg_starts_with_name = false) noexcept;
static bool set_category (const char *name, const char *arg, size_t arg_length, unsigned int entry, bool arg_starts_with_name = false) noexcept;

private:
static inline LogTimingCategories _log_timing_categories;
Expand Down
53 changes: 40 additions & 13 deletions src/native/clr/include/runtime-base/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ namespace xamarin::android {
return create_directory (dir.data (), mode);
}

static void create_public_directory (std::string_view const& dir);
static auto monodroid_fopen (std::string_view const& filename, std::string_view const& mode) noexcept -> FILE*;
static void set_world_accessable (std::string_view const& path);
static void create_public_directory (const char *dir);
static auto monodroid_fopen (const char *filename, const char *mode) noexcept -> FILE*;
static void set_world_accessable (const char *path);
static auto set_world_accessible (int fd) noexcept -> bool;

// Puts higher half of the `value` byte as a hexadecimal character in `high_half` and
Expand Down Expand Up @@ -344,19 +344,46 @@ namespace xamarin::android {
return static_cast<ssize_t>(path_length);
}

static auto join_paths (char *stack_buffer, size_t stack_buffer_size, std::string_view first, std::string_view second) noexcept -> char*
// Formats a string that usually fits in a stack buffer, falling back to the heap when it
// does not. `formatter` must write into the buffer it is given and return the formatted
// length excluding the terminating NUL, or the negative required capacity including it —
// the protocol implemented by `format_joined_path()` and friends.
//
// Returns `stack_buffer`, or a heap block the caller must `std::free ()`. Compare the
// result against `stack_buffer` to tell the two apart. When `length` is not `nullptr`, it
// receives the formatted length excluding the terminating NUL.
template<typename TFormatter>
static auto format_with_retry (char *stack_buffer, size_t stack_buffer_size, TFormatter formatter, size_t *length = nullptr) noexcept -> char*
{
ssize_t result = format_joined_path (stack_buffer, stack_buffer_size, first, second);
if (result >= 0) {
return stack_buffer;
ssize_t result = formatter (stack_buffer, stack_buffer_size);
if (result < 0) {
size_t required_capacity = static_cast<size_t>(-result);
char *heap_buffer = static_cast<char*> (std::malloc (required_capacity));
abort_unless (heap_buffer != nullptr, "Failed to allocate formatted string");

result = formatter (heap_buffer, required_capacity);
abort_unless (result >= 0, "Failed to format string using the required capacity");
if (length != nullptr) {
*length = static_cast<size_t>(result);
}
return heap_buffer;
}

size_t required_capacity = static_cast<size_t>(-result);
char *heap_buffer = static_cast<char*> (std::malloc (required_capacity));
abort_unless (heap_buffer != nullptr, "Failed to allocate joined path");
result = format_joined_path (heap_buffer, required_capacity, first, second);
abort_unless (result >= 0, "Failed to join path using the required capacity");
return heap_buffer;
if (length != nullptr) {
*length = static_cast<size_t>(result);
}
return stack_buffer;
}

static auto join_paths (char *stack_buffer, size_t stack_buffer_size, std::string_view first, std::string_view second) noexcept -> char*
{
return format_with_retry (
stack_buffer,
stack_buffer_size,
[first, second](char *buffer, size_t buffer_size) noexcept {
return format_joined_path (buffer, buffer_size, first, second);
}
);
}

private:
Expand Down
Loading