Skip to content

Commit da1d643

Browse files
[TrimmableTypeMap] Use non-generic JavaPeerProxy base for interface proxies
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 5e9346d commit da1d643

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
@@ -142,6 +142,16 @@ sealed class JavaPeerProxyData
142142
/// </summary>
143143
public bool IsGenericDefinition { get; init; }
144144

145+
/// <summary>
146+
/// True if the proxied peer type is a Java interface. Interfaces have no constructors, so
147+
/// the proxy must derive from the non-generic <c>JavaPeerProxy</c> base instead of
148+
/// <c>JavaPeerProxy&lt;T&gt;</c>: closing the generic (whose <c>T</c> is annotated with
149+
/// <c>[DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)]</c>) over an
150+
/// interface makes ILC fail to load the type (TypeLoadException). Instances are still created
151+
/// from <see cref="InvokerType"/> in CreateInstance.
152+
/// </summary>
153+
public bool IsInterface { get; init; }
154+
145155
/// <summary>
146156
/// True when the Java stub must not call RegisterNatives from a static initializer because
147157
/// 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
@@ -142,13 +142,13 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
142142
Assert.All (proxyTypes, proxyType => {
143143
switch (proxyType.BaseType.Kind) {
144144
case HandleKind.TypeSpecification:
145-
// Non-generic target types derive from the closed `JavaPeerProxy<T>`.
145+
// Concrete (constructible) target types derive from the closed `JavaPeerProxy<T>`.
146146
var baseTypeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) proxyType.BaseType);
147147
var baseTypeName = baseTypeSpec.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null);
148148
Assert.StartsWith ("Java.Interop.JavaPeerProxy`1<", baseTypeName, StringComparison.Ordinal);
149149
break;
150150
case HandleKind.TypeReference:
151-
// Open generic target types derive from the non-generic `JavaPeerProxy`.
151+
// Open generic definitions and interfaces derive from the non-generic `JavaPeerProxy`.
152152
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType);
153153
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
154154
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
@@ -165,6 +165,29 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
165165
objectProxyBaseType.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null));
166166
}
167167

168+
[Fact]
169+
public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase ()
170+
{
171+
// JavaPeerProxy<T> annotates T with [DynamicallyAccessedMembers(Constructors)]. Closing it
172+
// over an interface (which has no constructors) makes ILC fail to load the closed generic
173+
// type ("Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). Interface proxies must
174+
// therefore derive from the non-generic JavaPeerProxy base (a plain TypeReference).
175+
var peers = ScanFixtures ();
176+
using var stream = GenerateAssembly (peers);
177+
using var pe = new PEReader (stream);
178+
var reader = pe.GetMetadataReader ();
179+
180+
var interfaceProxy = reader.TypeDefinitions
181+
.Select (h => reader.GetTypeDefinition (h))
182+
.Where (t => reader.GetString (t.Namespace) == "_TypeMap.Proxies")
183+
.First (t => reader.GetString (t.Name) == "Android_Views_IOnClickListener_Proxy");
184+
185+
Assert.Equal (HandleKind.TypeReference, interfaceProxy.BaseType.Kind);
186+
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) interfaceProxy.BaseType);
187+
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
188+
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
189+
}
190+
168191
// Regression test: every generated proxy type must carry a custom attribute whose
169192
// constructor points at the proxy's own TypeDefinitionHandle (either as a MemberRef
170193
// parented on the TypeDef, or as a MethodDefinition on the TypeDef). This is how

0 commit comments

Comments
 (0)