diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index 21ce7d7d66d..25e9db22008 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -16,13 +16,6 @@ static class ModelBuilder { const string ProxyTypeSuffix = "_Proxy"; - // Workaround for https://github.com/dotnet/runtime/issues/127004 - // When true, all TypeMap entries are emitted as 2-arg (unconditional) to avoid the - // trimmer bug that strips TypeMapAssociation attributes when a TypeMap attribute - // references the same type. Set to false once the runtime bug is fixed to re-enable - // 3-arg conditional entries that allow unused framework bindings to be trimmed away. - const bool ForceUnconditionalEntries = true; - static readonly HashSet EssentialRuntimeTypes = new (StringComparer.Ordinal) { "java/lang/Object", "java/lang/Class", @@ -189,13 +182,7 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName, } // Base JNI name entry → alias holder (self-referencing trim target, kept alive by associations) - // When ForceUnconditionalEntries is true we MUST emit this as 2-arg (unconditional) just - // like BuildEntry does: dotnet/runtime#127004 strips the TypeMapAssociation that keeps the - // holder alive when a TypeMap entry references the same type, leaving the dictionary key - // missing at runtime and breaking hierarchy lookups for essential types like - // java/lang/String and java/lang/Object. - bool aliasBaseUnconditional = ForceUnconditionalEntries - || EssentialRuntimeTypes.Contains (jniName) + bool aliasBaseUnconditional = EssentialRuntimeTypes.Contains (jniName) || peersForName.Any (IsUnconditionalEntry); model.Entries.Add (new TypeMapAttributeData { JniName = jniName, @@ -406,9 +393,7 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr proxyRef = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName); } - // When ForceUnconditionalEntries is true, always emit 2-arg (unconditional) TypeMap - // attributes to work around https://github.com/dotnet/runtime/issues/127004. - bool isUnconditional = ForceUnconditionalEntries || IsUnconditionalEntry (peer); + bool isUnconditional = IsUnconditionalEntry (peer); string? targetRef = null; if (!isUnconditional) { targetRef = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName); diff --git a/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs b/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs index f1508cd11e0..da37a6aa1b8 100644 --- a/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs +++ b/src/Mono.Android/Xamarin.Android.Net/ServerCertificateCustomValidator.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Net.Security; +using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; using Android.OS; @@ -170,34 +171,34 @@ private sealed class AlwaysAcceptingHostnameVerifier : Java.Lang.Object, IHostna public bool Verify (string? hostname, ISSLSession? session) => true; } - [DynamicDependency(nameof(IX509TrustManager.CheckServerTrusted), typeof(IX509TrustManagerInvoker))] - [DynamicDependency(nameof(IX509TrustManager.CheckServerTrusted), typeof(X509ExtendedTrustManagerInvoker))] private static IX509TrustManager FindX509TrustManager(ITrustManager[] trustManagers, out int index) { + index = -1; + for (int i = 0; i < trustManagers.Length; i++) { var trustManager = trustManagers [i]; if (trustManager is IX509TrustManager x509TrustManager) { index = i; return x509TrustManager; } + } - // On API 21-23, the default Java trust manager is TrustManagerImpl from Conscrypt. The class implements X509TrustManager - // but the .NET pattern matching will fail in this case and we need to cast it explicitly. - int apiLevel = (int)Build.VERSION.SdkInt; - if (apiLevel <= 23) { - if (IsTrustManagerImpl (trustManager)) { - index = i; - return trustManager.JavaCast (); - } - } + if (trustManagers.Length > 10_000) { + HackToPreserveInvokers(trustManagers); } throw new InvalidOperationException($"Could not find {nameof(IX509TrustManager)} in {nameof(ITrustManager)} array."); + } - static bool IsTrustManagerImpl (ITrustManager trustManager) - { - var javaClassName = JNIEnv.GetClassNameFromInstance (trustManager.Handle); - return javaClassName.Equals ("com/android/org/conscrypt/TrustManagerImpl", StringComparison.Ordinal); + [MethodImpl (MethodImplOptions.NoInlining)] + static void HackToPreserveInvokers (ITrustManager[] trustManagers) + { + // HACK - make IX509TrustManagerInvoker visible to the linker so that it doesn't get trimmed out. + // These branches are unreachable, but the linker doesn't know that. + if (trustManagers.Length > 1_000_000) { + _ = new IX509TrustManagerInvoker (IntPtr.Zero, JniHandleOwnership.DoNotTransfer); + } else if (trustManagers.Length > 2_000_000) { + _ = new X509ExtendedTrustManagerInvoker (IntPtr.Zero, JniHandleOwnership.DoNotTransfer); } } diff --git a/src/Mono.Android/map.csv b/src/Mono.Android/map.csv index 9288c1f7144..ad20547d9b8 100644 --- a/src/Mono.Android/map.csv +++ b/src/Mono.Android/map.csv @@ -478,9 +478,9 @@ E,36,android/app/appfunctions/AppFunctionException.ERROR_ENTERPRISE_POLICY_DISAL E,36,android/app/appfunctions/AppFunctionException.ERROR_FUNCTION_NOT_FOUND,1003,Android.App.AppFunctions.AppFunctionError,FunctionNotFound,remove, E,36,android/app/appfunctions/AppFunctionException.ERROR_INVALID_ARGUMENT,1001,Android.App.AppFunctions.AppFunctionError,InvalidArgument,remove, E,36,android/app/appfunctions/AppFunctionException.ERROR_SYSTEM_ERROR,2000,Android.App.AppFunctions.AppFunctionError,SystemError,remove, -E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DEFAULT,0,Android.App.AppFunctions.AppFunctionState,Default,remove, -E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DISABLED,2,Android.App.AppFunctions.AppFunctionState,Disabled,remove, -E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_ENABLED,1,Android.App.AppFunctions.AppFunctionState,Enabled,remove, +E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DEFAULT,0,Android.App.AppFunctions.AppFunctionEnabledState,Default,remove, +E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_DISABLED,2,Android.App.AppFunctions.AppFunctionEnabledState,Disabled,remove, +E,36,android/app/appfunctions/AppFunctionManager.APP_FUNCTION_STATE_ENABLED,1,Android.App.AppFunctions.AppFunctionEnabledState,Enabled,remove, E,37,android/app/appfunctions/AppFunctionMetadata.SCOPE_ACTIVITY,1,Android.App.AppFunctions.AppFunctionMetadataScope,Activity,remove, E,37,android/app/appfunctions/AppFunctionMetadata.SCOPE_GLOBAL,0,Android.App.AppFunctions.AppFunctionMetadataScope,Global,remove, E,37,android/app/AppInteractionAttribution.INTERACTION_TYPE_OTHER,0,Android.App.AppInteractionAttributionInteractionType,Other,remove, diff --git a/src/Mono.Android/methodmap.csv b/src/Mono.Android/methodmap.csv index 2b452d40b49..bab127e450d 100644 --- a/src/Mono.Android/methodmap.csv +++ b/src/Mono.Android/methodmap.csv @@ -4100,7 +4100,7 @@ 36,android.app.appfunctions,AppFunctionException,getErrorCategory,return,Android.App.AppFunctions.AppFunctionErrorCategory 36,android.app.appfunctions,AppFunctionException,getErrorCode,return,Android.App.AppFunctions.AppFunctionError 36,android.app.appfunctions,AppFunctionException,writeToParcel,flags,Android.OS.ParcelableWriteFlags -36,android.app.appfunctions,AppFunctionManager,setAppFunctionEnabled,newEnabledState,Android.App.AppFunctions.AppFunctionState +36,android.app.appfunctions,AppFunctionManager,setAppFunctionEnabled,newEnabledState,Android.App.AppFunctions.AppFunctionEnabledState 36,android.app.appfunctions,ExecuteAppFunctionRequest,writeToParcel,flags,Android.OS.ParcelableWriteFlags 36,android.app.appfunctions,ExecuteAppFunctionResponse,writeToParcel,flags,Android.OS.ParcelableWriteFlags 36,android.app,ApplicationStartInfo,getStartComponent,return,Android.App.ApplicationStartInfoStartComponent diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 55ba4a3e9f9..7a34fc345fe 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -172,14 +172,12 @@ public void Build_UserAcwType_IsUnconditional () public void Build_McwBinding_IsTrimmable () { // MCW binding types (DoNotGenerateAcw=true) are trimmable unless essential. - // When ForceUnconditionalEntries is enabled (workaround for dotnet/runtime#127004), - // all entries become unconditional. var peer = MakeMcwPeer ("android/app/Activity", "Android.App.Activity", "Mono.Android") with { DoNotGenerateAcw = true }; var model = BuildModel (new [] { peer }); Assert.Single (model.Entries); - Assert.True (model.Entries [0].IsUnconditional); - Assert.Null (model.Entries [0].TargetTypeReference); + Assert.False (model.Entries [0].IsUnconditional); + Assert.Equal ("Android.App.Activity, Mono.Android", model.Entries [0].TargetTypeReference); } [Fact] @@ -248,8 +246,8 @@ public void Build_PeerWithActivation_CreatesNamedProxy (string jniName, string m [Fact] public void Build_SinglePeer_HasAssociation () { - // When ForceUnconditionalEntries is enabled, single peers emit associations - // so the runtime proxy type map is populated. + // Single peers with generated proxies emit associations so the runtime proxy + // type map is populated. var peer = MakePeerWithActivation ("my/app/MainActivity", "MyApp.MainActivity", "App"); var model = BuildModel (new [] { peer }, "MyTypeMap"); @@ -338,8 +336,8 @@ public void Fixture_McwBinding_IsTrimmable (string javaName) var peer = FindFixtureByJavaName (javaName); Assert.True (peer.DoNotGenerateAcw); var model = BuildModel (new [] { peer }); - // ForceUnconditionalEntries workaround makes all entries unconditional - Assert.True (model.Entries [0].IsUnconditional); + Assert.False (model.Entries [0].IsUnconditional); + Assert.NotNull (model.Entries [0].TargetTypeReference); } } @@ -776,7 +774,6 @@ public class PeBlobValidation [Fact] public void FullPipeline_Mixed2ArgAnd3Arg_BothSurviveRoundTrip () { - // With ForceUnconditionalEntries, both are emitted as 2-arg unconditional var objectPeer = FindFixtureByJavaName ("java/lang/Object"); var activityPeer = FindFixtureByJavaName ("android/app/Activity"); @@ -793,7 +790,7 @@ public void FullPipeline_Mixed2ArgAnd3Arg_BothSurviveRoundTrip () var activityEntry = attrs.FirstOrDefault (a => a.jniName == "android/app/Activity"); Assert.NotNull (activityEntry.jniName); - Assert.Null (activityEntry.targetRef); // unconditional due to ForceUnconditionalEntries + Assert.Equal ("Android.App.Activity, TestFixtures", activityEntry.targetRef); }); } @@ -818,22 +815,20 @@ public void FullPipeline_UnconditionalType_Emits2ArgAttribute (string javaName, } [Fact] - public void FullPipeline_McwBinding_Emits2ArgAttribute_WithWorkaround () + public void FullPipeline_McwBinding_Emits3ArgAttribute () { - // With ForceUnconditionalEntries workaround for dotnet/runtime#127004, - // MCW bindings are emitted as 2-arg unconditional. var peer = FindFixtureByJavaName ("android/app/Activity"); - var model = BuildModel (new [] { peer }, "Blob2ArgWorkaround"); + var model = BuildModel (new [] { peer }, "Blob3ArgConditional"); Assert.Single (model.Entries); - Assert.True (model.Entries [0].IsUnconditional); + Assert.False (model.Entries [0].IsUnconditional); - EmitAndVerify (model, "Blob2ArgWorkaround", (pe, reader) => { + EmitAndVerify (model, "Blob3ArgConditional", (pe, reader) => { var (jniName, proxyRef, targetRef) = ReadFirstTypeMapAttributeBlob (reader); Assert.Equal ("android/app/Activity", jniName); Assert.NotNull (proxyRef); Assert.Contains ("Android_App_Activity_Proxy", proxyRef!); - Assert.Null (targetRef); // unconditional due to ForceUnconditionalEntries + Assert.Equal ("Android.App.Activity, TestFixtures", targetRef); }); } }