Skip to content

Commit 7c81e12

Browse files
Propagate deferred registerNatives to base classes and fix test plumbing
- Propagate CannotRegisterInStaticConstructor through the base class chain so that base types like TestInstrumentation_1 also use the deferred __md_registerNatives() pattern instead of static { registerNatives(...); } which crashes before the managed runtime registers the JNI native. - Revert C++ host-jni.cc/hh registerNatives bridge — the managed [UnmanagedCallersOnly] registration in TrimmableTypeMap.RegisterNatives() handles this without needing a C++ bridge. - Add targetPackage default for instrumentation in ComponentElementBuilder. - Switch proxy base type to generic JavaPeerProxy<T> in TypeMapAssemblyEmitter. - Add CannotRegisterInStaticConstructor to JavaPeerProxyData model. - Normalize manifest android:name to actual JNI names. - Add test exclusions for TrimmableIgnore and SSL categories. - Add TRIMMABLE_TYPEMAP define constant for conditional compilation. - Add unit tests for base class propagation and manifest normalization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5b2b698 commit 7c81e12

11 files changed

Lines changed: 267 additions & 36 deletions

File tree

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ComponentElementBuilder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer)
176176
return;
177177
}
178178
PropertyMapper.ApplyMappings (element, component.Properties, PropertyMapper.InstrumentationMappings);
179+
if (element.Attribute (AndroidNs + "targetPackage") is null) {
180+
var packageName = (string?) manifest.Attribute ("package");
181+
if (!packageName.IsNullOrEmpty ()) {
182+
element.SetAttributeValue (AndroidNs + "targetPackage", packageName);
183+
}
184+
}
179185

180186
manifest.Add (element);
181187
}

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ sealed class JavaPeerProxyData
120120
/// </summary>
121121
public bool IsGenericDefinition { get; init; }
122122

123+
/// <summary>
124+
/// True when the Java stub must not call RegisterNatives from a static initializer because
125+
/// the type can be instantiated before the runtime is fully ready (for example Application
126+
/// or Instrumentation subclasses).
127+
/// </summary>
128+
public bool CannotRegisterInStaticConstructor { get; init; }
129+
123130
/// <summary>
124131
/// Whether this proxy needs ACW support (RegisterNatives + UCO wrappers + IAndroidCallableWrapper).
125132
/// </summary>

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
214214
},
215215
IsAcw = isAcw,
216216
IsGenericDefinition = peer.IsGenericDefinition,
217+
CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor,
217218
};
218219

219220
if (peer.InvokerTypeName != null) {

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ sealed class TypeMapAssemblyEmitter
7777
TypeReferenceHandle _systemTypeRef;
7878
TypeReferenceHandle _runtimeTypeHandleRef;
7979
TypeReferenceHandle _jniTypeRef;
80-
TypeReferenceHandle _trimmableNativeRegistrationRef;
8180
TypeReferenceHandle _notSupportedExceptionRef;
8281
TypeReferenceHandle _runtimeHelpersRef;
8382

@@ -87,7 +86,6 @@ sealed class TypeMapAssemblyEmitter
8786
MemberReferenceHandle _notSupportedExceptionCtorRef;
8887
MemberReferenceHandle _jniObjectReferenceCtorRef;
8988
MemberReferenceHandle _jniEnvDeleteRefRef;
90-
MemberReferenceHandle _activateInstanceRef;
9189
MemberReferenceHandle _withinNewObjectScopeRef;
9290
MemberReferenceHandle _ucoAttrCtorRef;
9391
BlobHandle _ucoAttrBlobHandle;
@@ -170,7 +168,7 @@ void EmitTypeReferences ()
170168
{
171169
var metadata = _pe.Metadata;
172170
_javaPeerProxyRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
173-
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy"));
171+
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy`1"));
174172
_iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef,
175173
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IJavaPeerable"));
176174
_jniHandleOwnershipRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
@@ -189,8 +187,6 @@ void EmitTypeReferences ()
189187
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle"));
190188
_jniTypeRef = metadata.AddTypeReference (_javaInteropRef,
191189
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniType"));
192-
_trimmableNativeRegistrationRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
193-
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("TrimmableNativeRegistration"));
194190
_notSupportedExceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
195191
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException"));
196192
_runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
@@ -250,14 +246,6 @@ void EmitMemberReferences ()
250246
p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
251247
}));
252248

253-
_activateInstanceRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "ActivateInstance",
254-
sig => sig.MethodSignature ().Parameters (2,
255-
rt => rt.Void (),
256-
p => {
257-
p.AddParameter ().Type ().IntPtr ();
258-
p.AddParameter ().Type ().Type (_systemTypeRef, false);
259-
}));
260-
261249
// JniEnvironment.get_WithinNewObjectScope() -> bool (static property)
262250
_withinNewObjectScopeRef = _pe.AddMemberRef (_jniEnvironmentRef, "get_WithinNewObjectScope",
263251
sig => sig.MethodSignature ().Parameters (0,
@@ -386,38 +374,55 @@ ExportMethodDispatchEmitter GetExportMethodDispatchEmitter ()
386374
void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinitionHandle> wrapperHandles)
387375
{
388376
var exportMethodDispatchEmitter = GetExportMethodDispatchEmitter ();
377+
378+
if (proxy.IsAcw) {
379+
// RegisterNatives uses RVA-backed UTF-8 fields under <PrivateImplementationDetails>.
380+
// Materialize those helper types before adding the proxy TypeDef, otherwise the
381+
// later RegisterNatives method can be attached to the helper type instead.
382+
foreach (var reg in proxy.NativeRegistrations) {
383+
_pe.GetOrAddUtf8Field (reg.JniMethodName);
384+
_pe.GetOrAddUtf8Field (reg.JniSignature);
385+
}
386+
}
387+
389388
var metadata = _pe.Metadata;
389+
var targetTypeRef = _pe.ResolveTypeRef (proxy.TargetType);
390+
var proxyBaseType = _pe.MakeGenericTypeSpec (_javaPeerProxyRef, targetTypeRef);
391+
var baseCtorRef = _pe.AddMemberRef (proxyBaseType, ".ctor",
392+
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2,
393+
rt => rt.Void (),
394+
p => {
395+
p.AddParameter ().Type ().String ();
396+
p.AddParameter ().Type ().Type (_systemTypeRef, false);
397+
}));
398+
390399
var typeDefHandle = metadata.AddTypeDefinition (
391400
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
392401
metadata.GetOrAddString (proxy.Namespace),
393402
metadata.GetOrAddString (proxy.TypeName),
394-
_javaPeerProxyRef,
403+
proxyBaseType,
395404
MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1),
396405
MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1));
397406

398407
if (proxy.IsAcw) {
399408
metadata.AddInterfaceImplementation (typeDefHandle, _iAndroidCallableWrapperRef);
400409
}
401410

402-
// .ctor — pass TargetType and InvokerType to base ctor
411+
// .ctor — pass the resolved JNI name and optional invoker type to the generic base proxy
403412
_pe.EmitBody (".ctor",
404413
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
405414
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }),
406415
encoder => {
407416
encoder.OpCode (ILOpCode.Ldarg_0);
408-
// arg 1: typeof(TargetType)
409-
encoder.OpCode (ILOpCode.Ldtoken);
410-
encoder.Token (_pe.ResolveTypeRef (proxy.TargetType));
411-
encoder.Call (_getTypeFromHandleRef);
412-
// arg 2: typeof(InvokerType) or null
417+
encoder.LoadString (metadata.GetOrAddUserString (proxy.JniName));
413418
if (proxy.InvokerType != null) {
414419
encoder.OpCode (ILOpCode.Ldtoken);
415420
encoder.Token (_pe.ResolveTypeRef (proxy.InvokerType));
416421
encoder.Call (_getTypeFromHandleRef);
417422
} else {
418423
encoder.OpCode (ILOpCode.Ldnull);
419424
}
420-
encoder.Call (_baseCtorRef);
425+
encoder.Call (baseCtorRef);
421426
encoder.OpCode (ILOpCode.Ret);
422427
});
423428

src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ public TrimmableTypeMapResult Execute (
3838
return new TrimmableTypeMapResult ([], [], allPeers);
3939
}
4040

41-
RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));
41+
var preparedManifest = PrepareManifestForRooting (manifestTemplate, manifestConfig);
42+
RootManifestReferencedTypes (allPeers, preparedManifest);
43+
PropagateDeferredRegistrationToBaseClasses (allPeers);
4244

4345
var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion);
4446
var jcwPeers = allPeers.Where (p =>
@@ -57,7 +59,7 @@ public TrimmableTypeMapResult Execute (
5759
}
5860

5961
var manifest = manifestConfig is not null
60-
? GenerateManifest (allPeers, assemblyManifestInfo, manifestConfig, manifestTemplate)
62+
? GenerateManifest (allPeers, assemblyManifestInfo, manifestConfig, preparedManifest)
6163
: null;
6264

6365
return new TrimmableTypeMapResult (generatedAssemblies, generatedJavaSources, allPeers, manifest, appRegTypes);
@@ -152,8 +154,7 @@ internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocumen
152154
XName attName = androidNs + "name";
153155
var packageName = (string?) root.Attribute ("package") ?? "";
154156

155-
var componentNames = new HashSet<string> (StringComparer.Ordinal);
156-
var deferredRegistrationNames = new HashSet<string> (StringComparer.Ordinal);
157+
var componentEntries = new List<(string Name, bool DeferredRegistration, XElement Element)> ();
157158
foreach (var element in root.Descendants ()) {
158159
switch (element.Name.LocalName) {
159160
case "application":
@@ -165,17 +166,13 @@ internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocumen
165166
var name = (string?) element.Attribute (attName);
166167
if (name is not null) {
167168
var resolvedName = ResolveManifestClassName (name, packageName);
168-
componentNames.Add (resolvedName);
169-
170-
if (element.Name.LocalName is "application" or "instrumentation") {
171-
deferredRegistrationNames.Add (resolvedName);
172-
}
169+
componentEntries.Add ((resolvedName, element.Name.LocalName is "application" or "instrumentation", element));
173170
}
174171
break;
175172
}
176173
}
177174

178-
if (componentNames.Count == 0) {
175+
if (componentEntries.Count == 0) {
179176
return;
180177
}
181178

@@ -189,10 +186,15 @@ internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocumen
189186
}
190187
}
191188

192-
foreach (var name in componentNames) {
189+
foreach (var (name, deferredRegistration, element) in componentEntries) {
193190
if (peersByDotName.TryGetValue (name, out var peers)) {
191+
string actualJavaName = JniSignatureHelper.JniNameToJavaName (peers [0].JavaName);
192+
if (!string.Equals ((string?) element.Attribute (attName), actualJavaName, StringComparison.Ordinal)) {
193+
element.SetAttributeValue (attName, actualJavaName);
194+
}
195+
194196
foreach (var peer in peers) {
195-
if (deferredRegistrationNames.Contains (name)) {
197+
if (deferredRegistration) {
196198
peer.CannotRegisterInStaticConstructor = true;
197199
}
198200

@@ -206,6 +208,39 @@ internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocumen
206208
}
207209
}
208210
}
211+
212+
/// <summary>
213+
/// Propagates <see cref="JavaPeerInfo.CannotRegisterInStaticConstructor"/> up the base class chain.
214+
/// When a type like NUnitInstrumentation has deferred registration, its base class
215+
/// TestInstrumentation_1 must also defer — otherwise the base class <c>&lt;clinit&gt;</c> will call
216+
/// <c>registerNatives</c> before the managed runtime is ready.
217+
/// </summary>
218+
static void PropagateDeferredRegistrationToBaseClasses (List<JavaPeerInfo> allPeers)
219+
{
220+
var peersByJniName = new Dictionary<string, JavaPeerInfo> (StringComparer.Ordinal);
221+
foreach (var peer in allPeers) {
222+
if (!peersByJniName.ContainsKey (peer.JavaName)) {
223+
peersByJniName [peer.JavaName] = peer;
224+
}
225+
}
226+
227+
foreach (var peer in allPeers) {
228+
if (!peer.CannotRegisterInStaticConstructor) {
229+
continue;
230+
}
231+
232+
var current = peer;
233+
while (current.BaseJavaName is { } baseJniName && peersByJniName.TryGetValue (baseJniName, out var basePeer)) {
234+
if (basePeer.DoNotGenerateAcw) {
235+
break;
236+
}
237+
238+
basePeer.CannotRegisterInStaticConstructor = true;
239+
current = basePeer;
240+
}
241+
}
242+
}
243+
209244
static void AddPeerByDotName (Dictionary<string, List<JavaPeerInfo>> peersByDotName, string dotName, JavaPeerInfo peer)
210245
{
211246
if (!peersByDotName.TryGetValue (dotName, out var list)) {

tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/ManifestGeneratorTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,22 @@ public void Instrumentation_GoesToManifest ()
253253
Assert.Null (appInstrumentation);
254254
}
255255

256+
[Fact]
257+
public void Instrumentation_UsesManifestPackageAsDefaultTargetPackage ()
258+
{
259+
var gen = CreateDefaultGenerator ();
260+
var peer = CreatePeer ("com/example/app/MyInstrumentation", new ComponentInfo {
261+
Kind = ComponentKind.Instrumentation,
262+
Properties = new Dictionary<string, object?> (),
263+
});
264+
265+
var doc = GenerateAndLoad (gen, [peer]);
266+
var instrumentation = doc.Root?.Element ("instrumentation");
267+
268+
Assert.NotNull (instrumentation);
269+
Assert.Equal ("com.example.app", (string?) instrumentation?.Attribute (AndroidNs + "targetPackage"));
270+
}
271+
256272
[Fact]
257273
public void RuntimeProvider_Added ()
258274
{

0 commit comments

Comments
 (0)