Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> EssentialRuntimeTypes = new (StringComparer.Ordinal) {
"java/lang/Object",
"java/lang/Class",
Expand Down Expand Up @@ -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);
Comment on lines +185 to 186
model.Entries.Add (new TypeMapAttributeData {
JniName = jniName,
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IX509TrustManager> ();
}
}
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);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Mono.Android/map.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/Mono.Android/methodmap.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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");

Expand All @@ -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);
});
}

Expand All @@ -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);
});
}
}
Expand Down