Skip to content

[Java.Interop] CreatePeer() must satisfy targetType #1308

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

Merged
merged 1 commit into from
Feb 15, 2025

Conversation

jonpryor
Copy link
Contributor

Context: dotnet/android#9747
Context: https://discord.com/channels/732297728826277939/732297837953679412/1339638847864176640
Context: https://discord.com/channels/732297728826277939/732297837953679412/1340011105510101063

While trying to get a MAUI sample running atop NativeAOT, we're observing the following crash:

E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidCastException: Arg_InvalidCastException
E AndroidRuntime:    at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x64
E AndroidRuntime:    at Microsoft.Maui.WindowOverlay.Initialize() + 0x168

Further investigation shows that the crash is from accessing the Activity.WindowManager property:

namespace Android.App;
partial class Activity {
  public virtual unsafe Android.Views.IWindowManager? WindowManager {
    // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='getWindowManager' and count(parameter)=0]"
    [Register ("getWindowManager", "()Landroid/view/WindowManager;", "GetGetWindowManagerHandler")]
    get {
      const string __id = "getWindowManager.()Landroid/view/WindowManager;";
      try {
        var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
        return global::Java.Lang.Object.GetObject<Android.Views.IWindowManager> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
      } finally {
      }
    }
  }
}

Object.GetObject<T>() is now a wrapper around
JniRuntime.JniValueManager.GetPeer(), so the problem, rephrased, is that this:

var peer = JniEnvironment.Runtime.ValueManager.GetPeer (
    ref h,
    JniObjectReferenceOptions.CopyAndDispose,
    targetType:typeof (IWindowManager));

returns a value which does not implement IWindowManager. It was, in fact, returning a Java.Lang.Object instance (!).

The cause of the bug is that JniRuntime.JniValueManager.CreatePeer() was not checking that the Type returned form
Runtime.TypeManager.GetType(JniTypeSignature) was compatible with targetType; instead, it returned the first type in the inheritance chain that had an activation constructor. This was Java.Lang.Object.

Later, when _GetObject<T>() tried to cast the return value of JniRuntime.JniValueManager.GetPeer() to IWindowManager, it failed.

Fix this by updating CreatePeer() to check that the Type from JniRuntime.JniTypeManager.GetType(JniTypeSignature) is assignable to targetType. This ensures that we don't return a Java.Lang.Object instance, allowing the cast to succeed.

Update JniRuntimeJniValueManagerContract to test these new semantics.

Context: dotnet/android#9747
Context: https://discord.com/channels/732297728826277939/732297837953679412/1339638847864176640
Context: https://discord.com/channels/732297728826277939/732297837953679412/1340011105510101063

While trying to get a MAUI sample running atop NativeAOT, we're
observing the following crash:

	E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidCastException: Arg_InvalidCastException
	E AndroidRuntime:    at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x64
	E AndroidRuntime:    at Microsoft.Maui.WindowOverlay.Initialize() + 0x168

Further investigation shows that the crash is from accessing the
`Activity.WindowManager` property:

	namespace Android.App;
	partial class Activity {
	  public virtual unsafe Android.Views.IWindowManager? WindowManager {
	    // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='getWindowManager' and count(parameter)=0]"
	    [Register ("getWindowManager", "()Landroid/view/WindowManager;", "GetGetWindowManagerHandler")]
	    get {
	      const string __id = "getWindowManager.()Landroid/view/WindowManager;";
	      try {
	        var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
	        return global::Java.Lang.Object.GetObject<Android.Views.IWindowManager> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
	      } finally {
	      }
	    }
	  }
	}

`Object.GetObject<T>()` is now a wrapper around
`JniRuntime.JniValueManager.GetPeer()`, so the problem, rephrased,
is that this:

	var peer = JniEnvironment.Runtime.ValueManager.GetPeer (
	    ref h,
	    JniObjectReferenceOptions.CopyAndDispose,
	    targetType:typeof (IWindowManager));

returns a value which *does not implement* `IWindowManager`.
It was, in fact, returning a `Java.Lang.Object` instance (!).

The cause of the bug is that `JniRuntime.JniValueManager.CreatePeer()`
was not checking that the `Type` returned form
`Runtime.TypeManager.GetType(JniTypeSignature)` was compatible with
`targetType`; instead, it returned the first type in the inheritance
chain that had an activation constructor.  This was `Java.Lang.Object`.

Later, when `_GetObject<T>()` tried to cast the return value of
`JniRuntime.JniValueManager.GetPeer()` to `IWindowManager`, it failed.

Fix this by updating `CreatePeer()` to check that the `Type` from
`JniRuntime.JniTypeManager.GetType(JniTypeSignature)` is assignable
to `targetType`.  This ensures that we *don't* return a
`Java.Lang.Object` instance, allowing the cast to succeed.

Update `JniRuntimeJniValueManagerContract` to test these new semantics.
jonpryor added a commit to dotnet/android that referenced this pull request Feb 14, 2025
Does It Build™?
@jonpryor
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.

Files not reviewed (1)
  • tests/Java.Interop-Tests/Java.Interop-Tests.csproj: Language not supported

@jonpryor jonpryor requested a review from jpobst February 15, 2025 14:52
@jonpryor jonpryor merged commit 2a7183a into main Feb 15, 2025
4 checks passed
@jonpryor jonpryor deleted the dev/jonp/jonp-CreatePeer-uses-fallback branch February 15, 2025 14:55
jonpryor added a commit to dotnet/android that referenced this pull request Feb 15, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Changes: dotnet/java-interop@d62008d...2a7183a

  * dotnet/java-interop@2a7183a1: [Java.Interop] CreatePeer() must satisfy targetType (dotnet/java-interop#1308)

Context: #9747
@github-actions github-actions bot locked and limited conversation to collaborators Mar 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants