Skip to content

Commit 1768282

Browse files
[TrimmableTypeMap] Use non-generic JavaPeerProxy base for interface proxies (#11769)
The generated proxy for an interface peer (e.g. a binding listener interface like ApxLabs.FastAndroidCamera.INonMarshalingPreviewCallback) derived from the closed generic JavaPeerProxy<TInterface>. That base annotates its type parameter with [DynamicallyAccessedMembers(PublicConstructors | NonPublicConstructors)] and returns new JavaPeerContainerFactory<T>() from GetContainerFactory(). Closing the generic over an interface -- which has no constructors -- makes ILC fail to load the closed type ("Failed to load type JavaPeerProxy1<...INonMarshalingPreviewCallback> from assembly Mono.Android"), which fails the whole NativeAOT build (ManifestTest.RemovePermissionTest, which pulls in ZXing.Net.Mobile -> ApxLabs.FastAndroidCamera). Interface peers now derive from the non-generic JavaPeerProxy base (the same base already used for open generic definitions), passing the interface as the TargetType constructor argument so runtime TargetType identity is unchanged. Instances are still created from the InvokerType in CreateInstance, so behaviour is preserved; abstract classes keep the generic base since they have constructors. Reproduced locally with ZXing.Net.Mobile (3.0.0-beta5): NativeAOT build failed with the TypeLoadException before, builds successfully after. Basic Mono.Android listener apps (IOnClickListener/IOnLongClickListener) and AndroidX.Fragment still build clean. Fixes RemovePermissionTest. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 504b78e commit 1768282

4 files changed

Lines changed: 48 additions & 7 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ sealed class JavaPeerProxyData
143143
/// </summary>
144144
public bool IsGenericDefinition { get; init; }
145145

146+
/// <summary>
147+
/// True if the proxied peer type is a Java interface. Interfaces have no constructors, so
148+
/// the proxy must derive from the non-generic <c>JavaPeerProxy</c> base instead of
149+
/// <c>JavaPeerProxy&lt;T&gt;</c>: closing the generic (whose <c>T</c> is annotated with
150+
/// <c>[DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)]</c>) over an
151+
/// interface makes ILC fail to load the type (TypeLoadException). Instances are still created
152+
/// from <see cref="InvokerType"/> in CreateInstance.
153+
/// </summary>
154+
public bool IsInterface { get; init; }
155+
146156
/// <summary>
147157
/// True when the Java stub must not call RegisterNatives from a static initializer because
148158
/// the type can be instantiated before the runtime is fully ready (for example Application

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
290290
},
291291
IsAcw = isAcw,
292292
IsGenericDefinition = peer.IsGenericDefinition,
293+
IsInterface = peer.IsInterface,
293294
CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor,
294295
};
295296

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -665,9 +665,16 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
665665
// placeholder like `Java.Lang.Object` leaks an incorrect TargetType into the typemap.
666666
// The non-generic base takes `targetType` as a ctor parameter, so we can pass the real
667667
// open-generic type token (a TypeRef, not a closed TypeSpec) and keep TargetType correct.
668+
//
669+
// Interface peers also use the non-generic base: `JavaPeerProxy<T>` annotates T with
670+
// [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)], and closing it
671+
// over an interface (which has no constructors) makes ILC fail to load the type
672+
// (TypeLoadException: "Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). The peer is
673+
// still activated from its InvokerType in CreateInstance, so behaviour is unchanged.
674+
bool useNonGenericBase = proxy.IsGenericDefinition || proxy.IsInterface;
668675
EntityHandle proxyBaseType;
669676
MemberReferenceHandle baseCtorRef;
670-
if (proxy.IsGenericDefinition) {
677+
if (useNonGenericBase) {
671678
proxyBaseType = _javaPeerProxyNonGenericRef;
672679
baseCtorRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, ".ctor",
673680
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
@@ -709,9 +716,9 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
709716
encoder => {
710717
encoder.OpCode (ILOpCode.Ldarg_0);
711718
encoder.LoadString (metadata.GetOrAddUserString (proxy.JniName));
712-
if (proxy.IsGenericDefinition) {
713-
// Non-generic base ctor signature: (string, Type, Type?). Push the open-generic
714-
// target type as the second argument.
719+
if (useNonGenericBase) {
720+
// Non-generic base ctor signature: (string, Type, Type?). Push the
721+
// target type (open-generic definition or interface) as the second argument.
715722
encoder.LoadToken (targetTypeRef);
716723
encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
717724
}
@@ -721,7 +728,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
721728
} else {
722729
encoder.OpCode (ILOpCode.Ldnull);
723730
}
724-
encoder.Call (baseCtorRef, parameterCount: proxy.IsGenericDefinition ? 3 : 2, isInstance: true);
731+
encoder.Call (baseCtorRef, parameterCount: useNonGenericBase ? 3 : 2, isInstance: true);
725732
encoder.Return ();
726733
});
727734

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,13 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
202202
Assert.All (proxyTypes, proxyType => {
203203
switch (proxyType.BaseType.Kind) {
204204
case HandleKind.TypeSpecification:
205-
// Non-generic target types derive from the closed `JavaPeerProxy<T>`.
205+
// Concrete (constructible) target types derive from the closed `JavaPeerProxy<T>`.
206206
var baseTypeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) proxyType.BaseType);
207207
var baseTypeName = baseTypeSpec.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null);
208208
Assert.StartsWith ("Java.Interop.JavaPeerProxy`1<", baseTypeName, StringComparison.Ordinal);
209209
break;
210210
case HandleKind.TypeReference:
211-
// Open generic target types derive from the non-generic `JavaPeerProxy`.
211+
// Open generic definitions and interfaces derive from the non-generic `JavaPeerProxy`.
212212
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType);
213213
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
214214
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
@@ -225,6 +225,29 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
225225
objectProxyBaseType.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null));
226226
}
227227

228+
[Fact]
229+
public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase ()
230+
{
231+
// JavaPeerProxy<T> annotates T with [DynamicallyAccessedMembers(Constructors)]. Closing it
232+
// over an interface (which has no constructors) makes ILC fail to load the closed generic
233+
// type ("Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). Interface proxies must
234+
// therefore derive from the non-generic JavaPeerProxy base (a plain TypeReference).
235+
var peers = ScanFixtures ();
236+
using var stream = GenerateAssembly (peers);
237+
using var pe = new PEReader (stream);
238+
var reader = pe.GetMetadataReader ();
239+
240+
var interfaceProxy = reader.TypeDefinitions
241+
.Select (h => reader.GetTypeDefinition (h))
242+
.Where (t => reader.GetString (t.Namespace) == "_TypeMap.Proxies")
243+
.First (t => reader.GetString (t.Name) == "Android_Views_IOnClickListener_Proxy");
244+
245+
Assert.Equal (HandleKind.TypeReference, interfaceProxy.BaseType.Kind);
246+
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) interfaceProxy.BaseType);
247+
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
248+
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
249+
}
250+
228251
// Regression test: every generated proxy type must carry a custom attribute whose
229252
// constructor points at the proxy's own TypeDefinitionHandle (either as a MemberRef
230253
// parented on the TypeDef, or as a MethodDefinition on the TypeDef). This is how

0 commit comments

Comments
 (0)