Skip to content
Draft
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 @@ -142,6 +142,16 @@ sealed class JavaPeerProxyData
/// </summary>
public bool IsGenericDefinition { get; init; }

/// <summary>
/// True if the proxied peer type is a Java interface. Interfaces have no constructors, so
/// the proxy must derive from the non-generic <c>JavaPeerProxy</c> base instead of
/// <c>JavaPeerProxy&lt;T&gt;</c>: closing the generic (whose <c>T</c> is annotated with
/// <c>[DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)]</c>) over an
/// interface makes ILC fail to load the type (TypeLoadException). Instances are still created
/// from <see cref="InvokerType"/> in CreateInstance.
/// </summary>
public bool IsInterface { get; init; }

/// <summary>
/// True when the Java stub must not call RegisterNatives from a static initializer because
/// the type can be instantiated before the runtime is fully ready (for example Application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
},
IsAcw = isAcw,
IsGenericDefinition = peer.IsGenericDefinition,
IsInterface = peer.IsInterface,
CannotRegisterInStaticConstructor = peer.CannotRegisterInStaticConstructor,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,9 +665,16 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
// placeholder like `Java.Lang.Object` leaks an incorrect TargetType into the typemap.
// The non-generic base takes `targetType` as a ctor parameter, so we can pass the real
// open-generic type token (a TypeRef, not a closed TypeSpec) and keep TargetType correct.
//
// Interface peers also use the non-generic base: `JavaPeerProxy<T>` annotates T with
// [DynamicallyAccessedMembers(PublicConstructors|NonPublicConstructors)], and closing it
// over an interface (which has no constructors) makes ILC fail to load the type
// (TypeLoadException: "Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). The peer is
// still activated from its InvokerType in CreateInstance, so behaviour is unchanged.
bool useNonGenericBase = proxy.IsGenericDefinition || proxy.IsInterface;
EntityHandle proxyBaseType;
MemberReferenceHandle baseCtorRef;
if (proxy.IsGenericDefinition) {
if (useNonGenericBase) {
proxyBaseType = _javaPeerProxyNonGenericRef;
baseCtorRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
Expand Down Expand Up @@ -709,9 +716,9 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
encoder => {
encoder.OpCode (ILOpCode.Ldarg_0);
encoder.LoadString (metadata.GetOrAddUserString (proxy.JniName));
if (proxy.IsGenericDefinition) {
// Non-generic base ctor signature: (string, Type, Type?). Push the open-generic
// target type as the second argument.
if (useNonGenericBase) {
// Non-generic base ctor signature: (string, Type, Type?). Push the
// target type (open-generic definition or interface) as the second argument.
encoder.LoadToken (targetTypeRef);
encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true);
}
Expand All @@ -721,7 +728,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<UcoWrapperTargetData, Me
} else {
encoder.OpCode (ILOpCode.Ldnull);
}
encoder.Call (baseCtorRef, parameterCount: proxy.IsGenericDefinition ? 3 : 2, isInstance: true);
encoder.Call (baseCtorRef, parameterCount: useNonGenericBase ? 3 : 2, isInstance: true);
encoder.Return ();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,13 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
Assert.All (proxyTypes, proxyType => {
switch (proxyType.BaseType.Kind) {
case HandleKind.TypeSpecification:
// Non-generic target types derive from the closed `JavaPeerProxy<T>`.
// Concrete (constructible) target types derive from the closed `JavaPeerProxy<T>`.
var baseTypeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle) proxyType.BaseType);
var baseTypeName = baseTypeSpec.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null);
Assert.StartsWith ("Java.Interop.JavaPeerProxy`1<", baseTypeName, StringComparison.Ordinal);
break;
case HandleKind.TypeReference:
// Open generic target types derive from the non-generic `JavaPeerProxy`.
// Open generic definitions and interfaces derive from the non-generic `JavaPeerProxy`.
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) proxyType.BaseType);
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
Expand All @@ -165,6 +165,29 @@ public void Generate_ProxyType_UsesGenericJavaPeerProxyBase ()
objectProxyBaseType.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null));
}

[Fact]
public void Generate_InterfaceProxyType_UsesNonGenericJavaPeerProxyBase ()
{
// JavaPeerProxy<T> annotates T with [DynamicallyAccessedMembers(Constructors)]. Closing it
// over an interface (which has no constructors) makes ILC fail to load the closed generic
// type ("Failed to load type 'JavaPeerProxy`1<ISomeInterface>'"). Interface proxies must
// therefore derive from the non-generic JavaPeerProxy base (a plain TypeReference).
var peers = ScanFixtures ();
using var stream = GenerateAssembly (peers);
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();

var interfaceProxy = reader.TypeDefinitions
.Select (h => reader.GetTypeDefinition (h))
.Where (t => reader.GetString (t.Namespace) == "_TypeMap.Proxies")
.First (t => reader.GetString (t.Name) == "Android_Views_IOnClickListener_Proxy");

Assert.Equal (HandleKind.TypeReference, interfaceProxy.BaseType.Kind);
var baseTypeRef = reader.GetTypeReference ((TypeReferenceHandle) interfaceProxy.BaseType);
Assert.Equal ("Java.Interop", reader.GetString (baseTypeRef.Namespace));
Assert.Equal ("JavaPeerProxy", reader.GetString (baseTypeRef.Name));
}

// Regression test: every generated proxy type must carry a custom attribute whose
// constructor points at the proxy's own TypeDefinitionHandle (either as a MemberRef
// parented on the TypeDef, or as a MethodDefinition on the TypeDef). This is how
Expand Down
Loading