Skip to content

Commit

Permalink
[Java.Interop] GetSimpleReferences(): fallback for GetTypeSignatures() (
Browse files Browse the repository at this point in the history
#1305)

Context: dotnet/android#9768

dotnet/android#9768 attempts to add types from `Java.Interop.dll` to
the .NET for Android typemaps, in order to fix Debug warnings like:

	I monodroid-assembly: typemap: unable to find mapping to a Java type from managed type 'Java.Interop.ManagedPeer, Java.Interop'

Unfortunately, the initial attempt to generate typemaps for
`Java.Interop.dll` caused the assertion:

	AssertGetJniTypeInfoForType (typeof (JavaArray<JavaObject>),    "[Ljava/lang/Object;",  false,  1);

within `Java.InteropTests.JniTypeManagerTests.GetTypeSignature_Type()`
to fail with:

	Expected string length 19 but was 33. Strings differ at index 0.
	Expected: "[Ljava/lang/Object;"
	But was:  "crc64d5d92128469ae06d/JavaArray_1"
	-----------^

The immediate cause of the failure is that
`JniRuntime.JniTypeManager.GetTypeSignature()` called
`JniRuntime.JniTypeManager.GetSimpleReference()` *before* it tried
to see if the type was `JavaArray<T>`.  As `Java.Interop.dll` was now
being processed for typemap purposes, and because `JavaArray<T>` did
not have a `[JniTypeSignatureAttribute]`, the typemap got the default
behavior of `crc64[hash…]`.

The broader cause is that `GetSimpleReference()` should be the
*fallback* implementation, used after all other attempts to get a
JNI name have failed.

Update `GetTypeSignature()` and `GetTypeSignatures()` so that
`GetSimpleReference()` and/or `GetSimpleReferences()` are in fact
treated as fallbacks.

Additionally, update `AssertGetJniTypeInfoForType()` to assert that
the value returned by `GetTypeSignature()` is the same as the value
return3ed by` GetTypeSignatures().First()`.  This was *commented* as
being the case, but we should *verify* that as well.

Finally, *move* the
`type.GetCustomAttribute<JniTypeSignatureAttribute()` and
`GetReplacementType()` logic into `GetSimpleReferences()`.  This 
This emphasizes the "fallback" nature of `GetSimpleReference()`,
adds an missing `GetReplacementType()` invocation from the
`GetTypeSignatures()` codepath, and this is what
[`AndroidTypeManager.GetSimpleReference()`][0] was already doing.

[0]: https://github.com/dotnet/android/blob/21c413195e300b6440eb437dade4f3a114e795f7/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L279-L289
  • Loading branch information
jonpryor authored Feb 12, 2025
1 parent 57f7bc8 commit d62008d
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 43 deletions.
2 changes: 2 additions & 0 deletions src/Java.Interop/Java.Interop/JavaArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace Java.Interop
{
[JniTypeSignature ("java/lang/Object", ArrayRank=1, GenerateJavaPeer=false)]
public abstract class JavaArray<T> : JavaObject, IList, IList<T>
{
internal delegate TArray ArrayCreator<TArray> (ref JniObjectReference reference, JniObjectReferenceOptions transfer)
Expand Down Expand Up @@ -362,6 +363,7 @@ public void Dispose ()
}
}

[JniTypeSignature ("java/lang/Object", ArrayRank=1, GenerateJavaPeer=false)]
public abstract class JavaPrimitiveArray<
[DynamicallyAccessedMembers (Constructors)]
T
Expand Down
1 change: 1 addition & 0 deletions src/Java.Interop/Java.Interop/JavaObjectArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Java.Interop
{
[JniTypeSignature ("java/lang/Object", ArrayRank=1, GenerateJavaPeer=false)]
public class JavaObjectArray<
[DynamicallyAccessedMembers (Constructors)]
T
Expand Down
75 changes: 34 additions & 41 deletions src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,36 +156,23 @@ public JniTypeSignature GetTypeSignature (Type type)
if (GetBuiltInTypeArraySignature (type, ref signature))
return signature.AddArrayRank (rank);

var simpleRef = GetSimpleReference (type);
if (simpleRef != null)
return new JniTypeSignature (simpleRef, rank, false);

var name = type.GetCustomAttribute<JniTypeSignatureAttribute> (inherit: false);
if (name != null) {
#if NET
var altRef = GetReplacementType (name.SimpleReference);
if (altRef != null) {
return new JniTypeSignature (altRef, name.ArrayRank + rank, name.IsKeyword);
}
#endif // NET
return new JniTypeSignature (name.SimpleReference, name.ArrayRank + rank, name.IsKeyword);
}

var isGeneric = type.IsGenericType;
var genericDef = isGeneric ? type.GetGenericTypeDefinition () : type;
if (isGeneric) {
if (genericDef == typeof (JavaArray<>) || genericDef == typeof (JavaObjectArray<>)) {
var r = GetTypeSignature (type.GenericTypeArguments [0]);
return r.AddArrayRank (rank + 1);
}
}

if (isGeneric) {
simpleRef = GetSimpleReference (genericDef);
if (simpleRef != null)
return new JniTypeSignature (simpleRef, rank, false);
var genericSimpleRef = GetSimpleReference (genericDef);
if (genericSimpleRef != null)
return new JniTypeSignature (genericSimpleRef, rank, false);
}

var simpleRef = GetSimpleReference (type);
if (simpleRef != null)
return new JniTypeSignature (simpleRef, rank, false);

return default;
}

Expand All @@ -194,44 +181,39 @@ public IEnumerable<JniTypeSignature> GetTypeSignatures (Type type)
{
AssertValid ();

if (type == null)
yield break;
if (type.ContainsGenericParameters)
throw new ArgumentException ($"'{type}' contains a generic type definition. This is not supported.", nameof (type));

type = GetUnderlyingType (type, out int rank);

var signature = new JniTypeSignature (null);
var signature = JniTypeSignature.Empty;
if (GetBuiltInTypeSignature (type, ref signature))
yield return signature.AddArrayRank (rank);
if (GetBuiltInTypeArraySignature (type, ref signature))
yield return signature.AddArrayRank (rank);

foreach (var simpleRef in GetSimpleReferences (type)) {
if (simpleRef == null)
continue;
yield return new JniTypeSignature (simpleRef, rank, false);
}

var name = type.GetCustomAttribute<JniTypeSignatureAttribute> (inherit: false);
if (name != null) {
yield return new JniTypeSignature (name.SimpleReference, name.ArrayRank + rank, name.IsKeyword);
}

var isGeneric = type.IsGenericType;
var genericDef = isGeneric ? type.GetGenericTypeDefinition () : type;
var isGeneric = type.IsGenericType;
var genericDef = isGeneric ? type.GetGenericTypeDefinition () : type;
if (isGeneric) {
if (genericDef == typeof(JavaArray<>) || genericDef == typeof(JavaObjectArray<>)) {
if (genericDef == typeof (JavaArray<>) || genericDef == typeof (JavaObjectArray<>)) {
var r = GetTypeSignature (type.GenericTypeArguments [0]);
yield return r.AddArrayRank (rank + 1);
}
}

if (isGeneric) {
foreach (var simpleRef in GetSimpleReferences (genericDef)) {
if (simpleRef == null)
foreach (var genericSimpleRef in GetSimpleReferences (genericDef)) {
if (genericSimpleRef == null)
continue;
yield return new JniTypeSignature (simpleRef, rank, false);
yield return new JniTypeSignature (genericSimpleRef, rank, false);
}
}

foreach (var simpleRef in GetSimpleReferences (type)) {
if (simpleRef == null)
continue;
yield return new JniTypeSignature (simpleRef, rank, false);
}
}

static Type GetUnderlyingType (Type type, out int rank)
Expand Down Expand Up @@ -266,7 +248,18 @@ protected virtual IEnumerable<string> GetSimpleReferences (Type type)
throw new ArgumentNullException (nameof (type));
if (type.IsArray)
throw new ArgumentException ("Array type '" + type.FullName + "' is not supported.", nameof (type));
return EmptyStringArray;

var name = type.GetCustomAttribute<JniTypeSignatureAttribute> (inherit: false);
if (name != null) {
var altRef = GetReplacementType (name.SimpleReference);
if (altRef != null) {
yield return altRef;
} else {
yield return name.SimpleReference;
}
}

yield break;
}

static readonly string[] EmptyStringArray = Array.Empty<string> ();
Expand Down
13 changes: 11 additions & 2 deletions tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,24 @@ public void GetTypeSignature_Type ()

#if !__ANDROID__
// Re-enable once typemap files contain `JavaObject` subclasses, not just Java.Lang.Object subclasses
//
// Note: dotnet/android@5c23bcda updates Java.Lang.Object to inherit JavaObject; this is not enough,
// as `<GenerateJavaStubs/>` only processes assemblies if they reference Mono.Android.dll.
AssertGetJniTypeInfoForType (typeof (GenericHolder<int>), GenericHolder<int>.JniTypeName, false, 0);
#endif // !__ANDROID__
}

static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeyword, int arrayRank)
{
var info = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (type);
Assert.AreEqual (jniType, info.Name);
Assert.AreEqual (arrayRank, info.ArrayRank);

// `GetTypeSignature() and `GetTypeSignatures()` should be "in sync"; verify that!
var info2 = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignatures (type).FirstOrDefault ();

Assert.AreEqual (jniType, info.Name, $"info.Name for `{type}`");
Assert.AreEqual (jniType, info2.Name, $"info.Name for `{type}`");
Assert.AreEqual (arrayRank, info.ArrayRank, $"info.ArrayRank for `{type}`");
Assert.AreEqual (arrayRank, info2.ArrayRank, $"info.ArrayRank for `{type}`");
}

[Test]
Expand Down

0 comments on commit d62008d

Please sign in to comment.