Skip to content
Merged
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 @@ -39,6 +39,7 @@ public TrimmableTypeMapResult Execute (
}

RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));
PropagateDeferredRegistrationToBaseClasses (allPeers);

var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion);
var jcwPeers = allPeers.Where (p =>
Expand Down Expand Up @@ -207,6 +208,42 @@ internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocumen
}
}

/// <summary>
/// Propagates <see cref="JavaPeerInfo.CannotRegisterInStaticConstructor"/> 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 <c>&lt;clinit&gt;</c> will call
/// <c>registerNatives</c> before the managed runtime is ready.
/// </summary>
internal static void PropagateDeferredRegistrationToBaseClasses (List<JavaPeerInfo> 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<JavaName, List<Peer>>
// lookup over all peers up front.
foreach (var peer in allPeers) {
if (peer.CannotRegisterInStaticConstructor) {
PropagateToAncestors (peer.BaseJavaName, allPeers);
}
}

static void PropagateToAncestors (string? baseJniName, List<JavaPeerInfo> allPeers)
{
while (baseJniName is not null) {
string? nextBase = null;
foreach (var basePeer in allPeers) {
if (!string.Equals (basePeer.JavaName, baseJniName, StringComparison.Ordinal) || basePeer.DoNotGenerateAcw) {
continue;
}

basePeer.CannotRegisterInStaticConstructor = true;
nextBase = basePeer.BaseJavaName;
}

baseJniName = nextBase;
}
}
}

static void AddPeerByDotName (Dictionary<string, List<JavaPeerInfo>> peersByDotName, string dotName, JavaPeerInfo peer)
{
if (!peersByDotName.TryGetValue (dotName, out var list)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,52 @@ public void RootManifestReferencedTypes_RootsApplicationAndInstrumentationTypes
Assert.True (peers [1].CannotRegisterInStaticConstructor, "Instrumentation type should defer Runtime.registerNatives().");
}

[Fact]
public void PropagateDeferredRegistrationToBaseClasses_PropagatesToBaseClassesOfManifestReferencedTypes ()
{
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<JavaPeerInfo> { basePeer, midPeer, leafPeer };

var doc = System.Xml.Linq.XDocument.Parse ("""
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example">
<instrumentation android:name="crc64ccc.NUnitInstrumentation" />
</manifest>
""");

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 ()
{
Expand Down