From 854682ba357ba0b10c0e28d1a22e281c51443478 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 21 Sep 2026 11:53:14 -0700 Subject: [PATCH] Fix Android NativeAOT template for updated memory safety rules (#134212) Fixes the Android NativeAOT build failure in the runtime-extra-platforms pipeline after System.Text.Json source-generation tests enabled `updated-memory-safety-rules`. The Android test template relied on type-level `unsafe` contexts, which have no effect under the updated rules. Move the contexts to the pointer operations and add explicit unsafe contracts to the native imports. The template remains compatible with the legacy rules. Validation: - `./build.sh tasks` - Compiled the template with legacy memory safety rules - Compiled the template with updated memory safety rules - Compiled the `SINGLE_FILE_TEST_RUNNER` path with updated memory safety rules > [!NOTE] > This PR description was generated with GitHub Copilot. Copilot-Session: e8bb4900-bf72-4876-b479-091185e5b89a --- .../Templates/monodroid-nativeaot.cs | 156 ++++++++++-------- 1 file changed, 91 insertions(+), 65 deletions(-) diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid-nativeaot.cs b/src/tasks/AndroidAppBuilder/Templates/monodroid-nativeaot.cs index 736d7cc33893e2..abee78d348cf15 100644 --- a/src/tasks/AndroidAppBuilder/Templates/monodroid-nativeaot.cs +++ b/src/tasks/AndroidAppBuilder/Templates/monodroid-nativeaot.cs @@ -16,14 +16,20 @@ namespace MonoDroid.NativeAOT; #pragma warning disable IDE0060 // Remove unused parameter -internal static unsafe partial class MonoDroidExports +internal static partial class MonoDroidExports { // void Java_net_dot_MonoRunner_setEnv (JNIEnv* env, jobject thiz, jstring j_key, jstring j_value); [UnmanagedCallersOnly(EntryPoint = "Java_net_dot_MonoRunner_setEnv", CallConvs = [typeof(CallConvCdecl)])] public static void SetEnv(JNIEnv* env, JObject thiz, JString j_key, JString j_value) { - string? key = env->GetStringUTFChars(j_key); - string? value = env->GetStringUTFChars(j_value); + string? key; + string? value; + unsafe + { + key = env->GetStringUTFChars(j_key); + value = env->GetStringUTFChars(j_value); + } + Console.WriteLine($"SetEnv: {key ?? "null"} = {value ?? "null"}"); if (key != null && value != null) { @@ -36,70 +42,78 @@ public static void SetEnv(JNIEnv* env, JObject thiz, JString j_key, JString j_va public static int InitRuntime(JNIEnv* env, JObject thiz, JString j_files_dir, JString j_entryPointLibName, long current_local_time) { Console.WriteLine("Initializing Android crypto native library"); - // The NativeAOT runtime does not need to be initialized, but the crypto library does. - JavaVM* javaVM = env->GetJavaVM(); - AndroidCryptoNative_InitLibraryOnLoad(javaVM, null); - var filesDir = env->GetStringUTFChars(j_files_dir) ?? string.Empty; + string filesDir; + unsafe + { + // The NativeAOT runtime does not need to be initialized, but the crypto library does. + JavaVM* javaVM = env->GetJavaVM(); + AndroidCryptoNative_InitLibraryOnLoad(javaVM, null); + filesDir = env->GetStringUTFChars(j_files_dir) ?? string.Empty; + } + AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", filesDir); Environment.CurrentDirectory = filesDir; return 0; } [LibraryImport("System.Security.Cryptography.Native.Android")] - internal static partial int AndroidCryptoNative_InitLibraryOnLoad(JavaVM* vm, void* reserved); + internal static unsafe partial int AndroidCryptoNative_InitLibraryOnLoad(JavaVM* vm, void* reserved); #if !SINGLE_FILE_TEST_RUNNER [DllImport("*", EntryPoint = "__managed__Main")] - static extern int ManagedMain(int argc, void** argv); + static extern unsafe int ManagedMain(int argc, void** argv); #endif // int Java_net_dot_MonoRunner_execEntryPoint (JNIEnv* env, jobject thiz, jstring j_entryPointLibName, jobjectArray j_args); [UnmanagedCallersOnly(EntryPoint = "Java_net_dot_MonoRunner_execEntryPoint", CallConvs = [typeof(CallConvCdecl)])] public static int ExecEntryPoint(JNIEnv* env, JObject thiz, JString j_entryPointLibName, JObjectArray j_args) { - int argc = env->GetArrayLength(j_args); - string[] args = new string[argc]; - for (int i = 0; i < argc; i++) + unsafe { - JObject j_arg = env->GetObjectArrayElement(j_args, i); - args[i] = env->GetStringUTFChars((JString)j_arg)!; - } + int argc = env->GetArrayLength(j_args); + string[] args = new string[argc]; + for (int i = 0; i < argc; i++) + { + JObject j_arg = env->GetObjectArrayElement(j_args, i); + args[i] = env->GetStringUTFChars((JString)j_arg)!; + } #if SINGLE_FILE_TEST_RUNNER - if (Environment.GetEnvironmentVariable("HOME") is string homeDir) - { - string excludesFile = Path.Combine(homeDir, "xunit-excludes.txt"); - if (File.Exists(excludesFile)) + if (Environment.GetEnvironmentVariable("HOME") is string homeDir) { - args = args.Concat(File.ReadAllLines(excludesFile).SelectMany(trait => new string[]{"-notrait", trait})).ToArray(); + string excludesFile = Path.Combine(homeDir, "xunit-excludes.txt"); + if (File.Exists(excludesFile)) + { + args = args.Concat(File.ReadAllLines(excludesFile).SelectMany(trait => new string[]{"-notrait", trait})).ToArray(); + } } - } - // SingleFile unit tests - return SingleFileTestRunner.Main(args); + // SingleFile unit tests + return SingleFileTestRunner.Main(args); #else - string entryPointName = env->GetStringUTFChars(j_entryPointLibName)!; - IntPtr[] managedMainArgs = new IntPtr[argc + 1]; - managedMainArgs[0] = Marshal.StringToCoTaskMemUTF8(entryPointName); - for (int i = 0; i < argc; i++) - { - managedMainArgs[i + 1] = Marshal.StringToCoTaskMemUTF8(args[i]); - } + string entryPointName = env->GetStringUTFChars(j_entryPointLibName)!; + IntPtr[] managedMainArgs = new IntPtr[argc + 1]; + managedMainArgs[0] = Marshal.StringToCoTaskMemUTF8(entryPointName); + for (int i = 0; i < argc; i++) + { + managedMainArgs[i + 1] = Marshal.StringToCoTaskMemUTF8(args[i]); + } - int ret; - fixed (IntPtr* argvPtrs = managedMainArgs) - { - void** argv = (void**)argvPtrs; - ret = ManagedMain(argc + 1, argv); - } + int ret; + fixed (IntPtr* argvPtrs = managedMainArgs) + { + void** argv = (void**)argvPtrs; + ret = ManagedMain(argc + 1, argv); + } - for (int i = 0; i < managedMainArgs.Length; i++) - { - Marshal.FreeCoTaskMem(managedMainArgs[i]); - } + for (int i = 0; i < managedMainArgs.Length; i++) + { + Marshal.FreeCoTaskMem(managedMainArgs[i]); + } - return ret; + return ret; #endif + } } // void Java_net_dot_MonoRunner_freeNativeResources (JNIEnv* env, jobject thiz); @@ -113,59 +127,71 @@ public static void FreeNativeResources(JNIEnv* env, JObject thiz) [StructLayout(LayoutKind.Sequential)] -internal unsafe struct JNIEnv +internal struct JNIEnv { JNINativeInterface* NativeInterface; public string? GetStringUTFChars(JString str) { - fixed (JNIEnv* thisptr = &this) + unsafe { - byte* chars = NativeInterface->GetStringUTFChars(thisptr, str, null); - if (chars is null) - return null; - - try + fixed (JNIEnv* thisptr = &this) { - return Marshal.PtrToStringUTF8((nint)chars)!; - } - finally - { - NativeInterface->ReleaseStringUTFChars(thisptr, str, chars); + byte* chars = NativeInterface->GetStringUTFChars(thisptr, str, null); + if (chars is null) + return null; + + try + { + return Marshal.PtrToStringUTF8((nint)chars)!; + } + finally + { + NativeInterface->ReleaseStringUTFChars(thisptr, str, chars); + } } } } public JavaVM* GetJavaVM() { - fixed (JNIEnv* thisptr = &this) + unsafe { - JavaVM* vm; - int result = NativeInterface->GetJavaVM(thisptr, &vm); - if (result != 0) - return null; + fixed (JNIEnv* thisptr = &this) + { + JavaVM* vm; + int result = NativeInterface->GetJavaVM(thisptr, &vm); + if (result != 0) + return null; - return vm; + return vm; + } } } public JSize GetArrayLength(JObjectArray array) { - fixed (JNIEnv* thisptr = &this) + unsafe { - return NativeInterface->GetArrayLength(thisptr, array); + fixed (JNIEnv* thisptr = &this) + { + return NativeInterface->GetArrayLength(thisptr, array); + } } } public JObject GetObjectArrayElement(JObjectArray array, JSize index) { - fixed (JNIEnv* thisptr = &this) + unsafe { - return NativeInterface->GetObjectArrayElement(thisptr, array, index); + fixed (JNIEnv* thisptr = &this) + { + return NativeInterface->GetObjectArrayElement(thisptr, array, index); + } } } [StructLayout(LayoutKind.Sequential)] - unsafe struct JNINativeInterface + struct JNINativeInterface { void* reserved0; void* reserved1; @@ -484,7 +510,7 @@ unsafe struct JNINativeInterface } [StructLayout(LayoutKind.Sequential)] -internal unsafe struct JavaVM +internal struct JavaVM { JNIInvokeInterface* InvokeInterface;