Skip to content

Commit 8a3e162

Browse files
authored
[Java.Interop] Generic Type Definitions are allowed (#1336)
Context: dotnet/android#9913 Context: dotnet/android@de49d96 Fixes: #1324 dotnet/android#9913 updated `JNIEnv.GetJniName(Type)` to use `JniRuntime.JniTypeManager.GetTypeSignature(Type)`. It promptly failed on CI: E NUnit : : System.NotSupportedException : 'Java.InteropTests.GenericHolder`1[T]' contains a generic type definition. This is not supported. E NUnit : at Java.Interop.JniRuntime.JniTypeManager.GetTypeSignature(Type ) E NUnit : at Android.Runtime.JNIEnv.GetJniName(Type ) E NUnit : at Java.Interop.TypeManager.RegisterType(String , Type ) E NUnit : at Android.Runtime.JNIEnvInit.RegisterJniNatives(IntPtr , Int32 , IntPtr , IntPtr , Int32 ) E NUnit : at Java.Interop.JniEnvironment.Object.AllocObject(JniObjectReference ) E NUnit : at Java.Interop.JniType.AllocObject() E NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods.StartCreateInstance(String , Type , JniArgumentValue* ) E NUnit : at Java.Lang.Object..ctor() E NUnit : at Java.InteropTests.GenericHolder`1[[System.Int32, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]..ctor() E NUnit : at Java.InteropTests.JnienvTest.NewClosedGenericTypeWorks() E NUnit : at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args) E NUnit : at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags ) because of this check within `JniRuntime.JniTypeManager.GetTypeSignature()`: if (type.ContainsGenericParameters) throw new NotSupportedException ($"'{type}' contains a generic type definition. This is not supported."); Why is `type.ContainsGenericParameters` true? `type.ContainsGenericParameters` is true because XAJavaInterop1-style Java Callable Wrappers contain an Assembly Qualified Type Name, and if the underlying type is generic, then the assembly qualified type name mentions the type definition. Consider: // C# class GenericHolder<T> : Java.Lang.Object { } results in this Java Callable Wrapper snippet: // Java public /* partial */ class GenericHolder_1 { public static final String __md_methods; static { __md_methods = ""; mono.android.Runtime.register ("Java.InteropTests.GenericHolder`1, Mono.Android-Tests", GenericHolder_1.class, __md_methods); } } Note ``Java.InteropTests.GenericHolder`1, Mono.Android-Tests``: ``Type.GetType("Java.InteropTests.GenericHolder`1, Mono.Android-Tests")`` results in a type definition, in which `type.ContainsGenericParameters` is true. *Is this a problem*? No, it is not a problem *in type registration*, as this has worked in dotnet/android for many years at this point. Is this a problem *elsewhere*? Yes: 1. Instances of `GenericHolder_1` cannot be created *from Java*, because Java doesn't know what type parameters to provide, because of type erasure. 2. `[JavaCallable]` methods on generic types also cannot work, as only *one* method can be registered, while semantically a generic method is *N* methods (per type). Update `JniRuntime.JniTypeManager.GetTypeSignature()` to no longer check for `type.ContainsGenericParameters`. Update `JniTypeManagerTests` to assert that constructing instances from Java results in an exception. A "funny" thing happened when adding that test: it didn't assert! There are two reasons for this. The first is that the Java Callable Wrapper for `Java.InteropTests.GenericHolder<T>` *did not* have a Java constructor! Without a constructor, it had the default constructor, which has no such verification check. The cause of the missing constructor is that `Java.Interop.Tools.JavaCallableWrappers` needs to know the JNI signature of the constructor to generate, and the `JavaObject` default constructor didn't have `[JniConstructorSignature]`. Update the `JavaObject` default constructor appropriately: partial class JavaObject { [JniConstructorSignature ("()V")] public JavaObject () … } This causes the Java Callable Wrapper for `GenericHolder<T>` to have the desired constructor which calls `ManagedPeer.construct()`, which in turn is a prerequisite for throwing `NotSupportedException`. The second issue is that the `CannotCreateGenericHolderFromJava()` test *must* use `JNIEnv::AllocObject()` + `JNIEnv::CallNonvirtualVoidMethod()` to construct the instance, ***not*** `JNIEnv::NewObject()`, because we special-case `JNIEnv::NewObject()`; see also commit 1c99956.
1 parent d909947 commit 8a3e162

File tree

3 files changed

+25
-5
lines changed

3 files changed

+25
-5
lines changed

src/Java.Interop/Java.Interop/JavaObject.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public JavaObject (ref JniObjectReference reference, JniObjectReferenceOptions o
6464
Construct (ref reference, options);
6565
}
6666

67+
[global::Java.Interop.JniConstructorSignature ("()V")]
6768
public unsafe JavaObject ()
6869
: this (ref *InvalidJniObjectReference, JniObjectReferenceOptions.None)
6970
{

src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ public JniTypeSignature GetTypeSignature (Type type)
145145

146146
if (type == null)
147147
throw new ArgumentNullException (nameof (type));
148-
if (type.ContainsGenericParameters)
149-
throw new NotSupportedException ($"'{type}' contains a generic type definition. This is not supported.");
148+
150149

151150
type = GetUnderlyingType (type, out int rank);
152151

@@ -183,8 +182,6 @@ public IEnumerable<JniTypeSignature> GetTypeSignatures (Type type)
183182

184183
if (type == null)
185184
yield break;
186-
if (type.ContainsGenericParameters)
187-
throw new NotSupportedException ($"'{type}' contains a generic type definition. This is not supported.");
188185

189186
type = GetUnderlyingType (type, out int rank);
190187

tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public void GetTypeSignature_Type ()
1818
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (int[,])));
1919
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (int[,][])));
2020
Assert.Throws<ArgumentException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (int[][,])));
21-
Assert.Throws<NotSupportedException>(() => JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (Action<>)));
2221
Assert.AreEqual (null, JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (JniRuntimeTest)).SimpleReference);
2322

2423
AssertGetJniTypeInfoForType (typeof (string), "java/lang/String", false, 0);
@@ -91,6 +90,11 @@ public void GetTypeSignature_Type ()
9190
// Note: dotnet/android@5c23bcda updates Java.Lang.Object to inherit JavaObject; this is not enough,
9291
// as `<GenerateJavaStubs/>` only processes assemblies if they reference Mono.Android.dll.
9392
AssertGetJniTypeInfoForType (typeof (GenericHolder<int>), GenericHolder<int>.JniTypeName, false, 0);
93+
94+
// XAJavaInterop1 Java Callable Wrappers, as used in dotnet/android, may contain
95+
// `Runtime.register("Java.InteropTests.GenericHolder`1, Mono.Android-Tests", …)`,
96+
// which results in a generic type definition. Permit this.
97+
AssertGetJniTypeInfoForType (typeof (GenericHolder<>), GenericHolder<int>.JniTypeName, false, 0);
9498
#endif // !__ANDROID__
9599
}
96100

@@ -215,6 +219,24 @@ public void GetTypeSignature ()
215219
Assert.AreEqual (typeof (string), GetTypeForSimpleReference ("java/lang/String"));
216220
Assert.AreEqual (null, GetTypeForSimpleReference ("com/example/does/not/exist"));
217221
}
222+
223+
[Test]
224+
public void CanCreateGenericHolder ()
225+
{
226+
using var holder = new GenericHolder<int> ();
227+
}
228+
229+
[Test]
230+
public unsafe void CannotCreateGenericHolderFromJava ()
231+
{
232+
var signature = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (typeof (GenericHolder<>));
233+
using var type = new JniType (signature.Name);
234+
var ctor = type.GetConstructor ("()V");
235+
var instance = type.AllocObject ();
236+
237+
Assert.Throws<NotSupportedException>(() =>
238+
JniEnvironment.InstanceMethods.CallNonvirtualVoidMethod (instance, type.PeerReference, ctor));
239+
}
218240
}
219241

220242
[JniTypeSignature (JniTypeName)]

0 commit comments

Comments
 (0)