Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Java.Interop] Typemap support for JavaObject & [JniTypeSignature] #1181

Merged
merged 7 commits into from
Feb 2, 2024

Commits on Jan 24, 2024

  1. [Java.Interop] Typemap support for JavaObject & [JniTypeSignature]

    Context: dotnet/android#8543
    Context: dotnet/android#8625
    Context: #1168
    Context: def5bc0
    Context: 005c914
    
    dotnet/android#8543 tested PR #1168, was Totally Green™ --
    finding no issues -- and so we merged PR #1168 into 005c914.
    
    Enter dotnet/android#8625, which bumps xamarin-android to
    use def5bc0, which includes 005c914.  dotnet/android#8625
    contains *failing unit tests* (?!), including
    `Java.InteropTests.InvokeVirtualFromConstructorTests()`:
    
    	Java.Lang.LinkageError : net.dot.jni.test.CallVirtualFromConstructorDerived
    	----> System.NotSupportedException : Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=net/dot/jni/test/CallVirtualFromConstructorDerived ArrayRank=0 Keyword=False) .
    	   at Java.Interop.JniEnvironment.StaticMethods.GetStaticMethodID(JniObjectReference , String , String )
    	   at Java.Interop.JniType.GetStaticMethod(String , String )
    	   at Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo(String , String )
    	   at Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo(String )
    	   at Java.Interop.JniPeerMembers.JniStaticMethods.InvokeObjectMethod(String , JniArgumentValue* )
    	   at Java.InteropTests.CallVirtualFromConstructorDerived.NewInstance(Int32 value)
    	   at Java.InteropTests.InvokeVirtualFromConstructorTests.ActivationConstructor()
    	   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
    	   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
    	  --- End of managed Java.Lang.LinkageError stack trace ---
    	java.lang.NoClassDefFoundError: net.dot.jni.test.CallVirtualFromConstructorDerived
    		at crc643df67da7b13bb6b1.TestInstrumentation_1.n_onStart(Native Method)
    		at crc643df67da7b13bb6b1.TestInstrumentation_1.onStart(TestInstrumentation_1.java:35)
    		at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
    	Caused by: android.runtime.JavaProxyThrowable: [System.NotSupportedException]: Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=net/dot/jni/test/CallVirtualFromConstructorDerived ArrayRank=0 Keyword=False) .
    		at Java.Interop.ManagedPeer.GetTypeFromSignature(Unknown Source:0)
    		at Java.Interop.ManagedPeer.RegisterNativeMembers(Unknown Source:0)
    		at net.dot.jni.ManagedPeer.registerNativeMembers(Native Method)
    		at net.dot.jni.test.CallVirtualFromConstructorDerived.<clinit>(CallVirtualFromConstructorDerived.java:12)
    		... 3 more
    
    	--NotSupportedException
    	   at Java.Interop.ManagedPeer.GetTypeFromSignature(JniTypeManager , JniTypeSignature , String )
    	   at Java.Interop.ManagedPeer.RegisterNativeMembers(IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, IntPtr n_methods)
    
    :shocked-pikachu-face: (But dotnet/android#8543 was green!)
    
    The problem is twofold:
    
     1. 005c914 now requires the presence of typemap entries from e.g.
        `Java.InteropTests.CallVirtualFromConstructorDerived` to
        `net.dot.jni.test.CallVirtualFromConstructorDerived`.
    
     2. `Java.Interop.Tools.JavaCallableWrappers` et al doesn't create
        typemap entries for `Java.Interop.JavaObject` subclasses which
        have `[JniTypeSignature]`.
    
    Consequently, our units tests fail (and apparently weren't *run* on
    dotnet/android#8543?!  Still not what happened.)
    
    Fix typemap generation by adding a new `TypeDefinition.HasJavaPeer()`
    extension method to replace all the `.IsSubclassOf("Java.Lang.Object")`
    and similar checks, extending it to also check for
    `Java.Interop.JavaObject` and `Java.Interop.JavaException` base types.
    (Continuing to use base type checks is done instead of just relying
    on implementation of `Java.Interop.IJavaPeerable` as a performance
    optimization, as there could be *lots* of interface types to check.)
    
    Additionally, @jonathanpeppers -- while trying to investigate all
    this -- ran across a build failure:
    
    	obj\Debug\net9.0-android\android\src\java\lang\Object.java(7,15): javac.exe error JAVAC0000:  error: cyclic inheritance involving Object
    
    This suggests that `Java.Interop.Tools.JavaCallableWrappers` was
    encountering `Java.Interop.JavaObject` -- or some other type which
    has `[JniTypeSignature("java/lang/Object")]` -- which is why
    `java/lang/Object.java` was being generated.
    
    Audit all `[JniTypeSignature]` attributes, and add
    `GenerateJavaPeer=false` to all types which should *not* hava a
    Java Callable Wrapper generated for them.  This includes nearly
    everything within `Java.Interop-Tests.dll`.  (We want the typemaps!
    We *don't* want generated Java source, as we have hand-written Java
    peer types for those tests.)
    
    ---
    
    Aside: this project includes [T4 Text Templates][0].  To regenerate
    the output files *without involving Visual Studio*, you can install
    the [`dotnet-t4`][1] tool:
    
    	$ dotnet tool install --global dotnet-t4
    
    then run it separately for each `.tt` file:
    
    	$HOME/.dotnet/tools/t4 -o src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs \
    	  src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt
    
    [0]: https://learn.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022
    [1]: https://www.nuget.org/packages/dotnet-t4/
    jonpryor committed Jan 24, 2024
    Configuration menu
    Copy the full SHA
    b9aa5f7 View commit details
    Browse the repository at this point in the history

Commits on Jan 25, 2024

  1. Avoid multiple java/lang/Object bindings.

    A funny thing happened when b9aa5f7 ran on xamarin-android:
    unit tests started crashing!
    
    	E monodroid-assembly: typemap: unable to load assembly 'Java.Interop-Tests' when looking up managed type corresponding to Java type 'java/lang/Object'
    
    What appears to be happening is an Unfortunate Interaction™:
    
     1. `Java.Interop-Tests.dll` contained *multiple bindings* for
        `java/lang/Object`. e.g.
    
            [JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
            partial class JavaDisposedObject : JavaObject {
            }
    
     2. The typemap generator has no functionality to "prioritize" one
        binding vs. another; it's random.  As such, there is nothing to
        cause `Java.Lang.Object` to be used as the preferred binding for
        `java/lang/Object`.
    
    This meant that when we hit the typemap codepath in .NET Android,
    we looked for the C# type that corresponded to `java/lang/Object`,
    found *some random type* from `Java.Interop-Tests`, and…
    
    …and then we hit another oddity: that codepath only supported looking
    for C# types in assemblies which had already been loaded.  This was
    occurring during startup, so `Java.Interop-Tests` had not yet been
    loaded yet, so it errored out, returned `nullptr`, and later Android
    just aborts things:
    
    	F droid.NET_Test: runtime.cc:638] JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x79
    
    Just…eep!
    
    This didn't happen before because `Java.Interop.JavaObject` subclasses
    *didn't* participate in typemap generation.  b9aa5f7 *added* that
    support, introducing this unforeseen interaction.
    
    Fix this by *removing* all "alternate bindings" for `java/lang/Object`:
    
    	- [JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
    	+ [JniTypeSignature (JniTypeName)]
              partial class JavaDisposedObject : JavaObject {
                  internal const string JniTypeName = "net/dot/jni/test/JavaDisposedObject";
              }
    
    This implicitly requires that we now have a Java Callable Wrapper
    for this type, so update `Java.Interop-Tests.csproj` to run `jcw-gen`
    as part of the build process.  This ensures that we create the
    JCW for e.g. `JavaDisposedObject`.
    
    Finally, update `JavaVMFixture` to add the required typemap entries.
    
    These changes should allow .NET Android unit tests to run w/o crashing.
    jonpryor committed Jan 25, 2024
    Configuration menu
    Copy the full SHA
    6b3637d View commit details
    Browse the repository at this point in the history

Commits on Jan 26, 2024

  1. Configuration menu
    Copy the full SHA
    3e583fe View commit details
    Browse the repository at this point in the history

Commits on Jan 29, 2024

  1. Add [JniTypeSignature] to GenericHolder<T>.

    This type mapping isn't *actually* required, but it *is* used in
    `JavaVMFixture`, and it confuses people (me!) if things are
    inconsistent.
    jonpryor committed Jan 29, 2024
    Configuration menu
    Copy the full SHA
    18a7e02 View commit details
    Browse the repository at this point in the history

Commits on Jan 30, 2024

  1. Configuration menu
    Copy the full SHA
    5965317 View commit details
    Browse the repository at this point in the history

Commits on Jan 31, 2024

  1. Ensure that Java.Interop-Tests.csproj builds jcw-gen.csproj first.

    CI is failing with:
    
    	Java.Interop-Tests -> /Users/runner/work/1/s/bin/TestRelease-net7.0/Java.Interop-Tests.dll
    	Could not execute because the specified command or file was not found.
    	Possible reasons for this include:
    	  * You misspelled a built-in dotnet command.
    	  * You intended to execute a .NET program, but dotnet-/Users/runner/work/1/s/bin/Release-net7.0//jcw-gen.dll does not exist.
    	  * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH.
    	##[error]tests/Java.Interop-Tests/Java.Interop-Tests.targets(26,5): Error MSB3073: The command "dotnet "/Users/runner/work/1/s/bin/Release-net7.0//jcw-gen.dll" -v "/Users/runner/work/1/s/bin/TestRelease-net7.0/Java.Interop-Tests.dll" --codegen-target JavaInterop1 -o "obj/Release-net7.0//java" -L "/Users/runner/work/1/s/bin/Release-net7.0/ref/." -L "/Users/runner/work/1/s/bin/TestRelease-net7.0/ref/." -L "/Users/runner/hostedtoolcache/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.15/ref/net7.0/." -L "/Users/runner/.nuget/packages/microsoft.testplatform.testhost/17.5.0-preview-20221003-04/lib/netcoreapp3.1/." -L "/Users/runner/.nuget/packages/microsoft.codecoverage/17.5.0-preview-20221003-04/lib/netcoreapp3.1/." -L "/Users/runner/.nuget/packages/mono.linq.expressions/2.0.0/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/mono.options/6.12.0.148/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/newtonsoft.json/13.0.1/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/nuget.frameworks/5.11.0/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/nunit/3.13.2/lib/netstandard2.0/." -L "/Users/runner/work/1/s/external/xamarin-android-tools/bin/Release/net6.0/ref/."" exited with code 1.
    	/Users/runner/work/1/s/tests/Java.Interop-Tests/Java.Interop-Tests.targets(26,5): error MSB3073: The command "dotnet "/Users/runner/work/1/s/bin/Release-net7.0//jcw-gen.dll" -v "/Users/runner/work/1/s/bin/TestRelease-net7.0/Java.Interop-Tests.dll" --codegen-target JavaInterop1 -o "obj/Release-net7.0//java" -L "/Users/runner/work/1/s/bin/Release-net7.0/ref/." -L "/Users/runner/work/1/s/bin/TestRelease-net7.0/ref/." -L "/Users/runner/hostedtoolcache/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.15/ref/net7.0/." -L "/Users/runner/.nuget/packages/microsoft.testplatform.testhost/17.5.0-preview-20221003-04/lib/netcoreapp3.1/." -L "/Users/runner/.nuget/packages/microsoft.codecoverage/17.5.0-preview-20221003-04/lib/netcoreapp3.1/." -L "/Users/runner/.nuget/packages/mono.linq.expressions/2.0.0/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/mono.options/6.12.0.148/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/newtonsoft.json/13.0.1/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/nuget.frameworks/5.11.0/lib/netstandard2.0/." -L "/Users/runner/.nuget/packages/nunit/3.13.2/lib/netstandard2.0/." -L "/Users/runner/work/1/s/external/xamarin-android-tools/bin/Release/net6.0/ref/."" exited with code 1. [/Users/runner/work/1/s/tests/Java.Interop-Tests/Java.Interop-Tests.csproj]
    
    Add a `@(ProjectReference)` from `Java.Interop-Tests.csproj`
    to `jcw-gen.csproj` to ensure that `jcw-gen.csproj` is built first.
    jonpryor committed Jan 31, 2024
    Configuration menu
    Copy the full SHA
    1af1d9a View commit details
    Browse the repository at this point in the history

Commits on Feb 2, 2024

  1. Update JavaObjectTest.cs

    Fix typo.  (What's a "cit"?)
    jonpryor authored Feb 2, 2024
    Configuration menu
    Copy the full SHA
    7a3fd0c View commit details
    Browse the repository at this point in the history