Skip to content

Commit d406bbc

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 23ec9dc commit d406bbc

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
@@ -173,6 +173,16 @@ sealed class JavaPeerProxyData
173173
/// </summary>
174174
public bool IsGenericDefinition { get; init; }
175175

176+
/// <summary>
177+
/// True if the proxied peer type is a Java interface. Interfaces have no constructors, so
178+
/// the proxy must derive from the non-generic <c>JavaPeerProxy</c> base instead of
179+
/// <c>JavaPeerProxy&lt;T&gt;</c>: closing the generic (whose <c>T</c> is annotated with
180+
/// <c>[DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)]</c>) over an
181+
/// interface makes ILC fail to load the type (TypeLoadException). Instances are still created
182+
/// from <see cref="InvokerType"/> in CreateInstance.
183+
/// </summary>
184+
public bool IsInterface { get; init; }
185+
176186
/// <summary>
177187
/// True when the Java stub must not call RegisterNatives from a static initializer because
178188
/// 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
@@ -318,6 +318,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
318318
},
319319
IsAcw = isAcw,
320320
IsGenericDefinition = peer.IsGenericDefinition,
321+
IsInterface = peer.IsInterface,
321322
CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor,
322323
};
323324

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -719,9 +719,16 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
719719
// placeholder like `Java.Lang.Object` leaks an incorrect TargetType into the typemap.
720720
// The non-generic base takes `targetType` as a ctor parameter, so we can pass the real
721721
// open-generic type token (a TypeRef, not a closed TypeSpec) and keep TargetType correct.
722+
//
723+
// Interface peers also use the non-generic base: `JavaPeerProxy<T>` annotates T with
724+
// [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)], and closing it
725+
// over an interface (which has no constructors) makes ILC fail to load the type
726+
// (TypeLoadException: "Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). The peer is
727+
// still activated from its InvokerType in CreateInstance, so behaviour is unchanged.
728+
bool useNonGenericBase = proxy.IsGenericDefinition || proxy.IsInterface;
722729
EntityHandle proxyBaseType;
723730
MemberReferenceHandle baseCtorRef;
724-
if (proxy.IsGenericDefinition) {
731+
if (useNonGenericBase) {
725732
proxyBaseType = _javaPeerProxyNonGenericRef;
726733
baseCtorRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, ".ctor",
727734
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
@@ -763,9 +770,9 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
763770
encoder => {
764771
encoder.OpCode (ILOpCode.Ldarg_0);
765772
encoder.LoadString (metadata.GetOrAddUserString (proxy.JniName));
766-
if (proxy.IsGenericDefinition) {
767-
// Non-generic base ctor signature: (string, Type, Type?). Push the open-generic
768-
// target type as the second argument.
773+
if (useNonGenericBase) {
774+
// Non-generic base ctor signature: (string, Type, Type?). Push the
775+
// target type (open-generic definition or interface) as the second argument.
769776
encoder.LoadToken (targetTypeRef);
770777
encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
771778
}
@@ -775,7 +782,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
775782
} else {
776783
encoder.OpCode (ILOpCode.Ldnull);
777784
}
778-
encoder.Call (baseCtorRef, parameterCount: proxy.IsGenericDefinition ? 3 : 2, isInstance: true);
785+
encoder.Call (baseCtorRef, parameterCount: useNonGenericBase ? 3 : 2, isInstance: true);
779786
encoder.Return ();
780787
});
781788

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,13 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
178178
Assert.All (proxyTypes, proxyType => {
179179
switch (proxyType.BaseType.Kind) {
180180
case HandleKind.TypeSpecification:
181-
// Non-generic target types derive from the closed `JavaPeerProxy<T>`.
181+
// Concrete (constructible) target types derive from the closed `JavaPeerProxy<T>`.
182182
var baseTypeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) proxyType.BaseType);
183183
var baseTypeName = baseTypeSpec.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null);
184184
Assert.StartsWith ("Java.Interop.JavaPeerProxy`1<", baseTypeName, StringComparison.Ordinal);
185185
break;
186186
case HandleKind.TypeReference:
187-
// Open generic target types derive from the non-generic `JavaPeerProxy`.
187+
// Open generic definitions and interfaces derive from the non-generic `JavaPeerProxy`.
188188
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType);
189189
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
190190
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
@@ -201,6 +201,29 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
201201
objectProxyBaseType.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null));
202202
}
203203

204+
[Fact]
205+
public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase ()
206+
{
207+
// JavaPeerProxy<T> annotates T with [DynamicallyAccessedMembers(Constructors)]. Closing it
208+
// over an interface (which has no constructors) makes ILC fail to load the closed generic
209+
// type ("Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). Interface proxies must
210+
// therefore derive from the non-generic JavaPeerProxy base (a plain TypeReference).
211+
var peers = ScanFixtures ();
212+
using var stream = GenerateAssembly (peers);
213+
using var pe = new PEReader (stream);
214+
var reader = pe.GetMetadataReader ();
215+
216+
var interfaceProxy = reader.TypeDefinitions
217+
.Select (h => reader.GetTypeDefinition (h))
218+
.Where (t => reader.GetString (t.Namespace) == "_TypeMap.Proxies")
219+
.First (t => reader.GetString (t.Name) == "Android_Views_IOnClickListener_Proxy");
220+
221+
Assert.Equal (HandleKind.TypeReference, interfaceProxy.BaseType.Kind);
222+
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) interfaceProxy.BaseType);
223+
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
224+
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
225+
}
226+
204227
// Regression test: every generated proxy type must carry a custom attribute whose
205228
// constructor points at the proxy's own TypeDefinitionHandle (either as a MemberRef
206229
// parented on the TypeDef, or as a MethodDefinition on the TypeDef). This is how

0 commit comments

Comments
 (0)