Skip to content

Conversation

@grendello
Copy link
Contributor

When using Android.OS.Bundle to retrieve a collection for a specific
key one can either use the untyped Get method or one of the
generic Get*ArrayList methods to retrieve the list. In the first
case we will get back an IList instance, in the second case we'll
get an IList<T> instance. However, if one calls Get first and then
one of the generic methods next, for the same key, the result will be
an InvalidCastException thrown because we attempt to cast the IList
obtained from Get to IList<T> needed by the generic Get*ArrayList
method. This happens because the same native handle is used in both cases
and the first call to Get causes Java.Lang.Object to cache the IList
instance so that the second call to Get*ArrayList gets the cached object
instead of a new, properly typed, one.

The solution is to extend Java.Lang.Object.PeekObject to allow specifying
the desired type of the object corresponding to the native handle and, if
the requirement isn't met, evict the entry from the cache returning null
to the caller, thus letting it do the right thing by creating an instance
of a class with correct type.

Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=58405

if (res != null && res.Handle != IntPtr.Zero && JNIEnv.IsSameObject (handle, res.Handle))
if (res != null && res.Handle != IntPtr.Zero && JNIEnv.IsSameObject (handle, res.Handle)) {
if (requiredType != null && !requiredType.IsAssignableFrom (res.GetType ())) {
instances.Remove (handle);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that we need the instances.Remove() here.

Copy link
Contributor Author

@grendello grendello Aug 3, 2017

Choose a reason for hiding this comment

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

That would speed up (slightly) subsequent possible lookups though (and also I thought we'd want to update the cache with the new object at some point?)

Copy link
Contributor

Choose a reason for hiding this comment

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

It would only speed up subsequent lookups if the handle were replaced, which isn't possible to know within the context of PeekObject().

Additionally, this could very easily break things in obscure ways:

class Foo : Java.Lang.Object {}

...

var f = new Foo();
// f.Handle is registered in Object.instances
var error = JavaArray<int>.FromJniHandle (f.Handle, JniHandleOwnership.DoNotTransfer);
// f is no longer registered!

If Foo overrides any Java-side virtual methods, things will blow up the next time such a method is invoked.

Instance registration is full of peril. I can't see how this instances.Remove() is a good thing.

When using Android.OS.Bundle to retrieve a collection for a specific
key one can either use the untyped `Get` method or one of the
generic `Get*ArrayList` methods to retrieve the list. In the first
case we will get back an `IList` instance, in the second case we'll
get an `IList<T>` instance. However, if one calls `Get` first and then
one of the generic methods next, for the same key, the result will be
an InvalidCastException thrown because we attempt to cast the IList
obtained from `Get` to `IList<T>` needed by the generic `Get*ArrayList`
method. This happens because the same native handle is used in both cases
and the first call to `Get` causes `Java.Lang.Object` to cache the IList
instance so that the second call to `Get*ArrayList` gets the cached object
instead of a new, properly typed, one.

The solution is to extend `Java.Lang.Object.PeekObject` to allow specifying
the desired type of the object corresponding to the native handle and, if
the requirement isn't met, evict the entry from the cache returning `null`
to the caller, thus letting it do the right thing by creating an instance
of a class with correct type.

Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=58405
@dellis1972
Copy link
Contributor

build

@jonpryor jonpryor merged commit c04c952 into dotnet:master Aug 4, 2017
@grendello grendello deleted the bug58405 branch August 7, 2017 09:14
jonpryor pushed a commit that referenced this pull request Aug 10, 2017
Fixes: https://bugzilla.xamarin.com/show_bug.cgi?id=58405

When using `Android.OS.Bundle` to retrieve a collection for a specific
key one can either use the untyped `Get` method or one of the
generic `Get*ArrayList` methods to retrieve the list. In the first
case we will get back an `IList` instance, in the second case we'll
get an `IList<T>` instance. However, if one calls `Get` first and then
one of the generic methods next, for the same key, the result will be
a thrown `InvalidCastException` because we attempt to cast the `IList`
obtained from `Get` to `IList<T>` needed by the generic `Get*ArrayList`
method. This happens because the same native handle is used in both cases
and the first call to `Get` causes `Java.Lang.Object` to cache the `IList`
instance so that the second call to `Get*ArrayList` gets the cached object
instead of a new, properly typed, one.

The solution is to extend `Java.Lang.Object.PeekObject` to allow specifying
the desired type of the object corresponding to the native handle and, if
the requirement isn't met, evict the entry from the cache returning `null`
to the caller, thus letting it do the right thing by creating an instance
of a class with correct type.
@github-actions github-actions bot locked and limited conversation to collaborators Feb 5, 2024
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.

4 participants