From df26f029a3071f87b5486bec2b049e59e121f1b2 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 13 Apr 2026 17:51:49 +0200 Subject: [PATCH 1/7] [TrimmableTypeMap] Propagate deferred registerNatives to base classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Application and Instrumentation types use deferred registerNatives via __md_registerNatives() to avoid calling the JNI native before the managed runtime is ready. However, CannotRegisterInStaticConstructor was only set on types directly matched in the manifest — base classes in the hierarchy (e.g., TestInstrumentation) still used the static initializer pattern, causing UnsatisfiedLinkError crashes at startup. Add PropagateDeferredRegistrationToBaseClasses() which walks each flagged peer's BaseJavaName chain and propagates the deferred registration flag to all base peers with JCW stubs. This ensures the entire inheritance chain uses the lazy __md_registerNatives() pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 33 +++++++++++++ .../TrimmableTypeMapGeneratorTests.cs | 46 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index edb0171758f..11acfa64242 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -39,6 +39,7 @@ public TrimmableTypeMapResult Execute ( } RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig)); + PropagateDeferredRegistrationToBaseClasses (allPeers); var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion); var jcwPeers = allPeers.Where (p => @@ -207,6 +208,38 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen } } + /// + /// Propagates up the base class chain. + /// When a type like NUnitInstrumentation has deferred registration, its base class + /// TestInstrumentation_1 must also defer — otherwise the base class <clinit> will call + /// registerNatives before the managed runtime is ready. + /// + internal static void PropagateDeferredRegistrationToBaseClasses (List allPeers) + { + var peersByJniName = new Dictionary (StringComparer.Ordinal); + foreach (var peer in allPeers) { + if (!peersByJniName.ContainsKey (peer.JavaName)) { + peersByJniName [peer.JavaName] = peer; + } + } + + foreach (var peer in allPeers) { + if (!peer.CannotRegisterInStaticConstructor) { + continue; + } + + var current = peer; + while (current.BaseJavaName is { } baseJniName && peersByJniName.TryGetValue (baseJniName, out var basePeer)) { + if (basePeer.DoNotGenerateAcw) { + break; + } + + basePeer.CannotRegisterInStaticConstructor = true; + current = basePeer; + } + } + } + static void AddPeerByDotName (Dictionary> peersByDotName, string dotName, JavaPeerInfo peer) { if (!peersByDotName.TryGetValue (dotName, out var list)) { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs index 22bafb43711..3328b1e2225 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs @@ -226,6 +226,52 @@ public void RootManifestReferencedTypes_RootsApplicationAndInstrumentationTypes Assert.True (peers [1].CannotRegisterInStaticConstructor, "Instrumentation type should defer Runtime.registerNatives()."); } + [Fact] + public void Execute_PropagatesDeferredRegistrationToBaseClasses () + { + var basePeer = new JavaPeerInfo { + JavaName = "crc64aaa/TestInstrumentation_1", CompatJniName = "crc64aaa/TestInstrumentation_1", + ManagedTypeName = "Tests.TestInstrumentation`1", ManagedTypeNamespace = "Tests", ManagedTypeShortName = "TestInstrumentation`1", + AssemblyName = "Tests", IsUnconditional = false, + BaseJavaName = "android/app/Instrumentation", + }; + var midPeer = new JavaPeerInfo { + JavaName = "crc64bbb/NUnitTestInstrumentation", CompatJniName = "crc64bbb/NUnitTestInstrumentation", + ManagedTypeName = "Tests.NUnitTestInstrumentation", ManagedTypeNamespace = "Tests", ManagedTypeShortName = "NUnitTestInstrumentation", + AssemblyName = "Tests", IsUnconditional = false, + BaseJavaName = "crc64aaa/TestInstrumentation_1", + }; + var leafPeer = new JavaPeerInfo { + JavaName = "crc64ccc/NUnitInstrumentation", CompatJniName = "crc64ccc/NUnitInstrumentation", + ManagedTypeName = "Tests.NUnitInstrumentation", ManagedTypeNamespace = "Tests", ManagedTypeShortName = "NUnitInstrumentation", + AssemblyName = "Tests", IsUnconditional = false, + BaseJavaName = "crc64bbb/NUnitTestInstrumentation", + }; + var peers = new List { basePeer, midPeer, leafPeer }; + + var doc = System.Xml.Linq.XDocument.Parse (""" + + + + + """); + + var generator = CreateGenerator (); + generator.RootManifestReferencedTypes (peers, doc); + + // RootManifestReferencedTypes sets the flag only on the directly matched leaf + Assert.True (leafPeer.CannotRegisterInStaticConstructor, "Leaf instrumentation should have deferred registration after manifest rooting."); + Assert.False (midPeer.CannotRegisterInStaticConstructor, "Mid peer should NOT have deferred registration before propagation."); + Assert.False (basePeer.CannotRegisterInStaticConstructor, "Base peer should NOT have deferred registration before propagation."); + + // PropagateDeferredRegistrationToBaseClasses walks the BaseJavaName chain + TrimmableTypeMapGenerator.PropagateDeferredRegistrationToBaseClasses (peers); + + Assert.True (leafPeer.CannotRegisterInStaticConstructor, "Leaf instrumentation should still have deferred registration."); + Assert.True (midPeer.CannotRegisterInStaticConstructor, "Mid peer should have deferred registration after propagation."); + Assert.True (basePeer.CannotRegisterInStaticConstructor, "Base peer should have deferred registration after propagation."); + } + [Fact] public void RootManifestReferencedTypes_WarnsForUnresolvedTypes () { From 1b7082872c498b9b3fd7c18293263b32c456c1e5 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 13 Apr 2026 18:01:57 +0200 Subject: [PATCH 2/7] Address review: handle duplicate JNI names, rename test - Use Dictionary> to handle multiple peers sharing the same JavaName (alias entries). - Use BFS with Queue + visited set for robust hierarchy traversal. - Rename test to match what it actually exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 36 +++++++++++++------ .../TrimmableTypeMapGeneratorTests.cs | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index 11acfa64242..cbf0b47df7e 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -216,26 +216,42 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen /// internal static void PropagateDeferredRegistrationToBaseClasses (List allPeers) { - var peersByJniName = new Dictionary (StringComparer.Ordinal); + var peersByJniName = new Dictionary> (StringComparer.Ordinal); foreach (var peer in allPeers) { - if (!peersByJniName.ContainsKey (peer.JavaName)) { - peersByJniName [peer.JavaName] = peer; + if (!peersByJniName.TryGetValue (peer.JavaName, out var peers)) { + peers = []; + peersByJniName [peer.JavaName] = peers; } + + peers.Add (peer); } foreach (var peer in allPeers) { - if (!peer.CannotRegisterInStaticConstructor) { + if (!peer.CannotRegisterInStaticConstructor || peer.BaseJavaName is not { } baseJniName) { continue; } - var current = peer; - while (current.BaseJavaName is { } baseJniName && peersByJniName.TryGetValue (baseJniName, out var basePeer)) { - if (basePeer.DoNotGenerateAcw) { - break; + var pendingBaseJniNames = new Queue (); + var visitedPeers = new HashSet (); + pendingBaseJniNames.Enqueue (baseJniName); + + while (pendingBaseJniNames.Count > 0) { + var currentBaseJniName = pendingBaseJniNames.Dequeue (); + if (!peersByJniName.TryGetValue (currentBaseJniName, out var basePeers)) { + continue; } - basePeer.CannotRegisterInStaticConstructor = true; - current = basePeer; + foreach (var basePeer in basePeers) { + if (!visitedPeers.Add (basePeer) || basePeer.DoNotGenerateAcw) { + continue; + } + + basePeer.CannotRegisterInStaticConstructor = true; + + if (basePeer.BaseJavaName is { } nextBaseJniName) { + pendingBaseJniNames.Enqueue (nextBaseJniName); + } + } } } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs index 3328b1e2225..c80fdbb6936 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs @@ -227,7 +227,7 @@ public void RootManifestReferencedTypes_RootsApplicationAndInstrumentationTypes } [Fact] - public void Execute_PropagatesDeferredRegistrationToBaseClasses () + public void PropagateDeferredRegistrationToBaseClasses_PropagatesToBaseClassesOfManifestReferencedTypes () { var basePeer = new JavaPeerInfo { JavaName = "crc64aaa/TestInstrumentation_1", CompatJniName = "crc64aaa/TestInstrumentation_1", From 89df5e16cd2b1fa3c17979848c6d15501e423ba3 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 13 Apr 2026 18:07:49 +0200 Subject: [PATCH 3/7] Extract helper methods to reduce nesting depth Split PropagateDeferredRegistrationToBaseClasses into three focused static methods: BuildJniNameLookup, PropagateToAncestors, and the top-level orchestrator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index cbf0b47df7e..ce382fe259c 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -216,41 +216,51 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen /// internal static void PropagateDeferredRegistrationToBaseClasses (List allPeers) { - var peersByJniName = new Dictionary> (StringComparer.Ordinal); + var peersByJniName = BuildJniNameLookup (allPeers); + + foreach (var peer in allPeers) { + if (peer.CannotRegisterInStaticConstructor && peer.BaseJavaName is { } baseJniName) { + PropagateToAncestors (baseJniName, peersByJniName); + } + } + } + + static Dictionary> BuildJniNameLookup (List allPeers) + { + var lookup = new Dictionary> (StringComparer.Ordinal); foreach (var peer in allPeers) { - if (!peersByJniName.TryGetValue (peer.JavaName, out var peers)) { + if (!lookup.TryGetValue (peer.JavaName, out var peers)) { peers = []; - peersByJniName [peer.JavaName] = peers; + lookup [peer.JavaName] = peers; } peers.Add (peer); } - foreach (var peer in allPeers) { - if (!peer.CannotRegisterInStaticConstructor || peer.BaseJavaName is not { } baseJniName) { + return lookup; + } + + static void PropagateToAncestors (string startJniName, Dictionary> peersByJniName) + { + var pending = new Queue (); + var visited = new HashSet (StringComparer.Ordinal); + pending.Enqueue (startJniName); + + while (pending.Count > 0) { + var jniName = pending.Dequeue (); + if (!visited.Add (jniName) || !peersByJniName.TryGetValue (jniName, out var peers)) { continue; } - var pendingBaseJniNames = new Queue (); - var visitedPeers = new HashSet (); - pendingBaseJniNames.Enqueue (baseJniName); - - while (pendingBaseJniNames.Count > 0) { - var currentBaseJniName = pendingBaseJniNames.Dequeue (); - if (!peersByJniName.TryGetValue (currentBaseJniName, out var basePeers)) { + foreach (var peer in peers) { + if (peer.DoNotGenerateAcw) { continue; } - foreach (var basePeer in basePeers) { - if (!visitedPeers.Add (basePeer) || basePeer.DoNotGenerateAcw) { - continue; - } - - basePeer.CannotRegisterInStaticConstructor = true; + peer.CannotRegisterInStaticConstructor = true; - if (basePeer.BaseJavaName is { } nextBaseJniName) { - pendingBaseJniNames.Enqueue (nextBaseJniName); - } + if (peer.BaseJavaName is { } nextJniName) { + pending.Enqueue (nextJniName); } } } From cbd7aa797ace8730fe4c92da8aa6f3868fadf9a1 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 13 Apr 2026 18:09:44 +0200 Subject: [PATCH 4/7] Use local methods instead of class-level static methods Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index ce382fe259c..0b55d6c6b94 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -216,51 +216,51 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen /// internal static void PropagateDeferredRegistrationToBaseClasses (List allPeers) { - var peersByJniName = BuildJniNameLookup (allPeers); + var peersByJniName = buildJniNameLookup (allPeers); foreach (var peer in allPeers) { if (peer.CannotRegisterInStaticConstructor && peer.BaseJavaName is { } baseJniName) { - PropagateToAncestors (baseJniName, peersByJniName); + propagateToAncestors (baseJniName, peersByJniName); } } - } - static Dictionary> BuildJniNameLookup (List allPeers) - { - var lookup = new Dictionary> (StringComparer.Ordinal); - foreach (var peer in allPeers) { - if (!lookup.TryGetValue (peer.JavaName, out var peers)) { - peers = []; - lookup [peer.JavaName] = peers; + static Dictionary> buildJniNameLookup (List allPeers) + { + var lookup = new Dictionary> (StringComparer.Ordinal); + foreach (var peer in allPeers) { + if (!lookup.TryGetValue (peer.JavaName, out var peers)) { + peers = []; + lookup [peer.JavaName] = peers; + } + + peers.Add (peer); } - peers.Add (peer); + return lookup; } - return lookup; - } + static void propagateToAncestors (string startJniName, Dictionary> peersByJniName) + { + var pending = new Queue (); + var visited = new HashSet (StringComparer.Ordinal); + pending.Enqueue (startJniName); - static void PropagateToAncestors (string startJniName, Dictionary> peersByJniName) - { - var pending = new Queue (); - var visited = new HashSet (StringComparer.Ordinal); - pending.Enqueue (startJniName); - - while (pending.Count > 0) { - var jniName = pending.Dequeue (); - if (!visited.Add (jniName) || !peersByJniName.TryGetValue (jniName, out var peers)) { - continue; - } - - foreach (var peer in peers) { - if (peer.DoNotGenerateAcw) { + while (pending.Count > 0) { + var jniName = pending.Dequeue (); + if (!visited.Add (jniName) || !peersByJniName.TryGetValue (jniName, out var peers)) { continue; } - peer.CannotRegisterInStaticConstructor = true; + foreach (var peer in peers) { + if (peer.DoNotGenerateAcw) { + continue; + } + + peer.CannotRegisterInStaticConstructor = true; - if (peer.BaseJavaName is { } nextJniName) { - pending.Enqueue (nextJniName); + if (peer.BaseJavaName is { } nextJniName) { + pending.Enqueue (nextJniName); + } } } } From fab4f7810f04daf10d04524006b662f3ea0e664c Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 13 Apr 2026 18:10:24 +0200 Subject: [PATCH 5/7] Use local methods with PascalCase names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index 0b55d6c6b94..2ce30e8e1be 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -216,15 +216,15 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen /// internal static void PropagateDeferredRegistrationToBaseClasses (List allPeers) { - var peersByJniName = buildJniNameLookup (allPeers); + var peersByJniName = BuildJniNameLookup (allPeers); foreach (var peer in allPeers) { if (peer.CannotRegisterInStaticConstructor && peer.BaseJavaName is { } baseJniName) { - propagateToAncestors (baseJniName, peersByJniName); + PropagateToAncestors (baseJniName, peersByJniName); } } - static Dictionary> buildJniNameLookup (List allPeers) + static Dictionary> BuildJniNameLookup (List allPeers) { var lookup = new Dictionary> (StringComparer.Ordinal); foreach (var peer in allPeers) { @@ -239,7 +239,7 @@ static Dictionary> buildJniNameLookup (List> peersByJniName) + static void PropagateToAncestors (string startJniName, Dictionary> peersByJniName) { var pending = new Queue (); var visited = new HashSet (StringComparer.Ordinal); From 140a42256bcb52e38c2796aaf3a028fd179fb41f Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 13 Apr 2026 18:19:18 +0200 Subject: [PATCH 6/7] Simplify propagation to linear scan Only 1-2 types need propagation in practice, so a linear scan per ancestor is simpler and cheaper than building a dictionary lookup over all peers up front. Drop Dictionary, Queue, and HashSet. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 52 ++++++------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index 2ce30e8e1be..98f908480af 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -216,52 +216,30 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen /// internal static void PropagateDeferredRegistrationToBaseClasses (List allPeers) { - var peersByJniName = BuildJniNameLookup (allPeers); - + // In practice only 1–2 types need propagation (one Application, maybe one + // Instrumentation), each with a short base-class chain. A linear scan per + // ancestor is simpler and cheaper than building a Dictionary> + // lookup over all peers up front. foreach (var peer in allPeers) { - if (peer.CannotRegisterInStaticConstructor && peer.BaseJavaName is { } baseJniName) { - PropagateToAncestors (baseJniName, peersByJniName); - } - } - - static Dictionary> BuildJniNameLookup (List allPeers) - { - var lookup = new Dictionary> (StringComparer.Ordinal); - foreach (var peer in allPeers) { - if (!lookup.TryGetValue (peer.JavaName, out var peers)) { - peers = []; - lookup [peer.JavaName] = peers; - } - - peers.Add (peer); + if (peer.CannotRegisterInStaticConstructor) { + PropagateToAncestors (peer.BaseJavaName, allPeers); } - - return lookup; } - static void PropagateToAncestors (string startJniName, Dictionary> peersByJniName) + static void PropagateToAncestors (string? baseJniName, List allPeers) { - var pending = new Queue (); - var visited = new HashSet (StringComparer.Ordinal); - pending.Enqueue (startJniName); - - while (pending.Count > 0) { - var jniName = pending.Dequeue (); - if (!visited.Add (jniName) || !peersByJniName.TryGetValue (jniName, out var peers)) { - continue; - } - - foreach (var peer in peers) { - if (peer.DoNotGenerateAcw) { + while (baseJniName is not null) { + string? nextBase = null; + foreach (var basePeer in allPeers) { + if (basePeer.JavaName != baseJniName || basePeer.DoNotGenerateAcw) { continue; } - peer.CannotRegisterInStaticConstructor = true; - - if (peer.BaseJavaName is { } nextJniName) { - pending.Enqueue (nextJniName); - } + basePeer.CannotRegisterInStaticConstructor = true; + nextBase = basePeer.BaseJavaName; } + + baseJniName = nextBase; } } } From dd8c92d66602beadca7e80ed8f6465bb00d34190 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 14 Apr 2026 09:04:40 +0200 Subject: [PATCH 7/7] Use Ordinal comparison for JNI names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TrimmableTypeMapGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index 98f908480af..78ae91c7cfb 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -231,7 +231,7 @@ static void PropagateToAncestors (string? baseJniName, List allPee while (baseJniName is not null) { string? nextBase = null; foreach (var basePeer in allPeers) { - if (basePeer.JavaName != baseJniName || basePeer.DoNotGenerateAcw) { + if (!string.Equals (basePeer.JavaName, baseJniName, StringComparison.Ordinal) || basePeer.DoNotGenerateAcw) { continue; }