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

Remove Desc<JClass> implementation for JObject and &JObject #412

Merged
merged 2 commits into from
Feb 7, 2023

Conversation

rib
Copy link
Contributor

@rib rib commented Feb 1, 2023

It was surprising that these called .get_object_class() and let code pass object instances in place of any class argument.

A simpler implementation that would instead transmute the reference from a JObject to a JClass (like GlobalRef does) would make it too easy to pass an instance as a class which can lead to undefined behaviour for some JNI apis (such as JNIEnv::new_object())

Fixes: #118

--

For reference; I looked at adding a unit test to verify that you can't just pass an instance as a class any more but realized that doesn't just lead to a recoverable jni error / exception - it's undefined behaviour (at least for NewObjectA) and trying this I see an error like: FATAL ERROR in native method: JNI received a class argument that is not a class and the process aborts.

Although it's not something changed by this PR; it seems worth highlighting that currently "safe" APIs like like new_object don't double check that a given JClass really corresponds to a class, even though they could trigger undefined behaviour if that's not the case. (further it's also notable that NewObjectA additionally requires that the class "must not refer to an array class" which isn't something that's checked)

That should generally be OK since it shouldn't really be possible to get a JClass that isn't a class without unsafe code but one exception to that currently is the Desc<JClass> implementation for GlobalRef that simply assumes its safe to transmute the global reference into a JClass (and so passing a global reference to an instance to JNIEnv::new_object can lead to undefined behaviour - with or without this change.)

We have discussed before that GlobalRef should ideally be revised at some point to not erase the type of reference it holds, and that could go some way to making things safer here.

A bit of a side thought, but considering the overlooked requirement for NewObjectA that the class "must not refer to an array class" makes me wonder if new_object should perhaps have its own wrapper type for a validated JClass that would additionally validate the class isn't an array but also allow the reference to be cached via a GlobalRef (that wouldn't erase the type) so this validation wouldn't need to be repeated if instantiating the same type. (This is just thinking out loud for something that should be dealt with as a separate issue though imho) edit: (redundant since NewObjectA won't be reached before failing to find an array class constructor)

Definition of Done

  • There are no TODOs left in the code
  • Change is covered by automated tests
  • The coding guidelines are followed
  • Public API has documentation
  • User-visible changes are mentioned in the Changelog
  • The continuous integration build passes

@rib rib added this to the 0.21 milestone Feb 1, 2023
@rib rib force-pushed the no-jclass-desc-for-jobject branch from 2d35279 to ca7097c Compare February 1, 2023 18:02
@argv-minus-one
Copy link
Contributor

For reference; I looked at adding a unit test to verify that you can't just pass an instance as a class any more but realized that doesn't just lead to a recoverable jni error / exception - it's undefined behaviour (at least for NewObjectA) and trying this I see an error like: FATAL ERROR in native method: JNI received a class argument that is not a class and the process aborts.

That's probably because the tests use -Xcheck:jni, which adds checks for some JNI programming errors, apparently including this one, and reliably aborts the process instead of UB. Without -Xcheck:jni, though, that would indeed be UB.

Although it's not something changed by this PR; it seems worth highlighting that currently "safe" APIs like like new_object don't double check that a given JClass really corresponds to a class, even though they could trigger undefined behaviour if that's not the case. (further it's also notable that NewObjectA additionally requires that the class "must not refer to an array class" which isn't something that's checked)

Have you tried that? I would expect it to fail reliably without UB when GetMethodID tries to look up a constructor for the array class. Per the documentation for java.lang.Class.getConstructors, array classes don't have any constructors to look up.

That should generally be OK since it shouldn't really be possible to get a JClass that isn't a class without unsafe code

True, but note that it is possible to get a JClass for an array class without unsafe code.

@rib
Copy link
Contributor Author

rib commented Feb 1, 2023

Have you tried that? I would expect it to fail reliably without UB when GetMethodID tries to look up a constructor for the array class. Per the documentation for java.lang.Class.getConstructors, array classes don't have any constructors to look up.

I didn't try it out yet - I just noticed that specific restriction when I ended up looking at the spec for NewObjectA.

It sounds quite likely that we'd get a reliable runtime error via the MethodID lookup, like you say, which would be convenient here so there wouldn't be any need to have other explicit checks that he class isn't an array class.

It was surprising that these called `.get_object_class()` and let code
pass object instances in place of any class argument.

A simpler implementation that would instead transmute the reference from
a JObject to a JClass (like GlobalRef does) would make it too easy to
pass an instance as a class which can lead to undefined behaviour for
some JNI apis (such as JNIEnv::new_object())

Fixes: jni-rs#118
This test verifies that we get a safe, runtime error if we try and
instantiate an array object via `env.new_object()` considering that
the JNI spec states that `NewObjectA` "must not refer to an array
class".

Our expectation is that `env.new_object` will never get as far as
calling `NewObjectA` in this case since it will first fail to
find any constructor `MethodId` for an array class; consistent
with how Java's `Class::getConstructors()` method doesn't expose
array class constructors.
@rib rib force-pushed the no-jclass-desc-for-jobject branch from ca7097c to fe30084 Compare February 1, 2023 22:08
@rib
Copy link
Contributor Author

rib commented Feb 1, 2023

I've added a test that confirms that env.new_object() returns with a runtime error if you try and pass it an array class.

@rib
Copy link
Contributor Author

rib commented Feb 6, 2023

Just a quick ping @argv-minus-one - any objection to me landing this PR now?

@rib rib mentioned this pull request Feb 6, 2023
Copy link
Contributor

@argv-minus-one argv-minus-one left a comment

Choose a reason for hiding this comment

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

Nope. Looks good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inconsistency in Desc<JClass> implementations for reference types
2 participants