Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -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