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

Use Unsafe API in more correct way #1719

Closed
wants to merge 1 commit into from
Closed

Conversation

alsemenov
Copy link

  • pass result of Unsafe.staticFieldBase() instead of Lookup.class to conform with specification for Unsafe.getObject() requirements.
  • avoid calling Unsafe.getObject() via reflection

Fixes #1672

@google-cla
Copy link

google-cla bot commented May 2, 2023

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@sameb
Copy link
Member

sameb commented May 2, 2023

Hi @alsemenov -- it looks like the CLA check failed. GitHub sees the email address associated with the PR as "Aleksei Semenov alsemenov@users.noreply.github.com", which likely isn't the actual email. I'm pretty sure the CLA check needs to know the actual email, so it can validate it.

@alsemenov
Copy link
Author

Hi @sameb
Thank you for pointing out on my incorrect e-mail address.
I have updated the PR with the correct address.

@@ -56,7 +59,8 @@ final class HiddenClassDefiner implements ClassDefiner {
@Override
public Class<?> define(Class<?> hostClass, byte[] bytecode) throws Exception {
Lookup trustedLookup =
(Lookup) GET_OBJECT_METHOD.invoke(THE_UNSAFE, Lookup.class, TRUSTED_LOOKUP_OFFSET);
(Lookup) ((sun.misc.Unsafe)THE_UNSAFE)
Copy link
Member

@sameb sameb May 2, 2023

Choose a reason for hiding this comment

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

Both AnonymousClassDefiner and other parts of HiddenClassDefiner still refer to sun.misc.Unsafe via reflection. Updating just this one tiny part to directly access it feels weird, and it also leaves the code messy: GET_OBJECT_METHOD is still defined but now unused.

Additionally, AFAICT, this is effectively reverting the code back to doing what it was doing before 3399615, which was submitted because of #1672. Based on this PR, I'm inclined to agree with @mcculls comment @ #1672 (comment). The TL;DR of that comment is: "The code looks like it was already doing the right thing, except doing it via reflection. It's possible the 'not really an Object' is making the reflective call fail. Using Lookup.class directly avoids the problem."

IIUC, this PR is attempting to revert back to using staticFieldBase instead of Lookup.class, and is avoiding the original problem by not using reflection to do the call. If I'm understanding correctly, can you clarify why using Lookup.class is not a valid solution?

Copy link
Author

Choose a reason for hiding this comment

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

Both AnonymousClassDefiner and other parts of HiddenClassDefiner still refer to sun.misc.Unsafe via reflection. Updating just this one tiny part to directly access it feels weird, and it also leaves the code messy: GET_OBJECT_METHOD is still defined but now unused.

Accessing sun.misc.Unsafe via reflection is legal thing. However passing result of Unsafe.staticFieldBase() via reflection is not ok. Because result of Unsafe.staticFieldBase() might be just a "cookie" instead of real java object and is not intended for such usage.

I updated the PR and removed the GET_OBJECT_METHOD. I see no reason to update AnonymousClassDefiner, because it does not use Unsafe.staticFieldBase().

can you clarify why using Lookup.class is not a valid solution?

Let's read descriptions of Unsafe methods:

    /**
     * Fetches a reference value from a given Java variable.
     * @see #getInt(Object, long)
     */
    @ForceInline
    public Object getObject(Object o, long offset) {
   /**
     * Fetches a value from a given Java variable.
     * More specifically, fetches a field or array element within the given
     * object {@code o} at the given offset, or (if {@code o} is null)
     * from the memory address whose numerical value is the given offset.
     * <p>
     * The results are undefined unless one of the following cases is true:
     * <ul>
     * <li>The offset was obtained from {@link #objectFieldOffset} on
     * the {@link java.lang.reflect.Field} of some Java field and the object
     * referred to by {@code o} is of a class compatible with that
     * field's class.
     *
     * <li>The offset and object reference {@code o} (either null or
     * non-null) were both obtained via {@link #staticFieldOffset}
     * and {@link #staticFieldBase} (respectively) from the
     * reflective {@link Field} representation of some Java field.
     *
     * <li>The object referred to by {@code o} is an array, and the offset
     * is an integer of the form {@code B+N*S}, where {@code N} is
     * a valid index into the array, and {@code B} and {@code S} are
     * the values obtained by {@link #arrayBaseOffset} and {@link
     * #arrayIndexScale} (respectively) from the array's class.  The value
     * referred to is the {@code N}<em>th</em> element of the array.
     *
     * </ul>
     * <p>
     * If one of the above cases is true, the call references a specific Java
     * variable (field or array element).  However, the results are undefined
     * if that variable is not in fact of the type returned by this method.
     * <p>
     ... 
     */
    @ForceInline
    public int getInt(Object o, long offset) {

We clearly fall into second case, which requires both object and offset to be obtained from staticFieldBase and staticFieldOffset. If, for example, Unsafe.staticFieldBase() returns some kind of a wrapper around Lookup.class, then Unsafe.getObject() would expect to get back that wrapper along with offset. Passing bare Lookup.class will break the Unsafe.getObject()

Copy link
Contributor

Choose a reason for hiding this comment

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

Before #1672 we were following the second case and used both staticFieldBase and staticFieldOffset - using this results in a JVM crash with Azul Prime, so as a workaround we used the class as a replacement for staticFieldBase.

I wasn't able to test directly with Azul Prime because I don't have a license for it - but I found other OSS projects, such as Apache/Felix, were using the class directly for staticFieldBase. Since I couldn't find similar crash reports in those OSS projects involving Azul Prime and there are several products that build on Apache/Felix, I inferred that this would be an acceptable workaround for #1672

If this is not the case and Azul Prime expects staticFieldBase (ie. different to the class) then I suggest we revert 3399615 so that we are at least consistent with the official usage in the javadoc.

I'd then suggest that a fix be made to Azul Prime so that it throws an exception instead of crashing the JVM - this would at least allow our code to fall back to alternative approaches.

Copy link
Author

Choose a reason for hiding this comment

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

The sole reason for Azul Prime crash is passing result of Unsafe.staticFieldBase() via reflection. Eventually reflection is replaced by on-the-fly generated method, which casts first argument to Object. Since the value is not real java object the cast cause VM to crash.

I'd then suggest that a fix be made to Azul Prime so that it throws an exception instead of crashing the JVM - this would at least allow our code to fall back to alternative approaches.

According to the Unsafe methods descriptions:

...
     * @throws RuntimeException No defined exceptions are thrown, not even
     *         {@link NullPointerException}
     */
    @ForceInline
    public int getInt(Object o, long offset) {

So we are not allowed to throw here.
Also additional check inside Unsafe.getObject() will cause undesirable performance degradation.

Copy link
Author

Choose a reason for hiding this comment

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

If this is not the case and Azul Prime expects staticFieldBase (ie. different to the class) then I suggest we revert 3399615 so that we are at least consistent with the official usage in the javadoc.

Yes, Azul Prime expects result of Unsafe.staticFieldBase() which is even not a real java object. Reverting 3399615 is fine with me. Should I submit another PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

@sameb yes if you're ok with direct references (guarded by try/catch) that's fine with me - the only potential issue is if/when javac restricts access to that class, but that would only limit the JDK used to build Guice and not the runtime version...

Copy link
Member

Choose a reason for hiding this comment

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

👍. Let's go with direct references throughout, which IIUC should also resolve this issue (if reverting back to the staticFieldBase). Could you (@mcculls) send a PR doing that?

Copy link
Member

Choose a reason for hiding this comment

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

@mcculls: I took a look at this locally and have a working version which I'll push out shortly. Turns out we still needed the reflection for defineAnonymousClass (b/c it's removed after 17+). Sorry for asking you to do work and then doing it myself!

Copy link

Choose a reason for hiding this comment

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

Hi @mcculls, did you download Prime and test #1722 with it?

Copy link
Member

Choose a reason for hiding this comment

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

FYI another user reported the latest code works with Azul, see #1732 (reply in thread).

Revert commit 3399615, because it contradicts with Unsafe.getObject() specification.
Both its arguments must be obtained from Unsafe.staticFieldBase() and
Unsafe.staticFieldOffset(). Submitting Lookup.class will lead to error,
if Unsafe.staticFieldBase() returns something different from Lookup.class.
@alsemenov
Copy link
Author

Changed the PR to revert 3399615

copybara-service bot pushed a commit that referenced this pull request May 4, 2023
…nsafe, and call getObject on it natively instead of through reflection. Calling getObject through reflection breaks Azul JVMs, because the staticFieldBase is a funny "not object", but when passed through reflection it tries to become an Object and fails.

Retrieval of the unsafe is based on how other google open source projects do it, see for example AbstractFuture: https://github.com/google/guava/blob/d4bd0c5ffa913104283e61aeb2c41bac641af042/guava/src/com/google/common/util/concurrent/AbstractFuture.java#L1340-L1365, UnsignedBytes: https://github.com/google/guava/blob/6405852bbf453b14d097b8ec3bcae494334b357d/android/guava/src/com/google/common/primitives/UnsignedBytes.java#L341-L366, and protobufs: https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java#L289-L315

FIXES #1719

PiperOrigin-RevId: 529205332
copybara-service bot pushed a commit that referenced this pull request May 4, 2023
…nsafe, and call getObject on it natively instead of through reflection. Calling getObject through reflection breaks Azul JVMs, because the staticFieldBase is a funny "not object", but when passed through reflection it tries to become an Object and fails.

Retrieval of the unsafe is based on how other google open source projects do it, see for example AbstractFuture: https://github.com/google/guava/blob/d4bd0c5ffa913104283e61aeb2c41bac641af042/guava/src/com/google/common/util/concurrent/AbstractFuture.java#L1340-L1365, UnsignedBytes: https://github.com/google/guava/blob/6405852bbf453b14d097b8ec3bcae494334b357d/android/guava/src/com/google/common/primitives/UnsignedBytes.java#L341-L366, and protobufs: https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java#L289-L315

FIXES #1719

PiperOrigin-RevId: 529205332
copybara-service bot pushed a commit that referenced this pull request May 4, 2023
…nsafe, and call getObject on it natively instead of through reflection. Calling getObject through reflection breaks Azul JVMs, because the staticFieldBase is a funny "not object", but when passed through reflection it tries to become an Object and fails.

Retrieval of the unsafe is based on how other google open source projects do it, see for example AbstractFuture: https://github.com/google/guava/blob/d4bd0c5ffa913104283e61aeb2c41bac641af042/guava/src/com/google/common/util/concurrent/AbstractFuture.java#L1340-L1365, UnsignedBytes: https://github.com/google/guava/blob/6405852bbf453b14d097b8ec3bcae494334b357d/android/guava/src/com/google/common/primitives/UnsignedBytes.java#L341-L366, and protobufs: https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java#L289-L315

FIXES #1719

PiperOrigin-RevId: 529205332
@sameb
Copy link
Member

sameb commented May 4, 2023

FYI I have an alternate version of this fix @ #1722 that I'll submit soon. (The failed checks are just an unrelated race condition in the test I need to fix.)

copybara-service bot pushed a commit that referenced this pull request May 4, 2023
…nsafe, and call getObject on it natively instead of through reflection.

Calling getObject through reflection breaks Azul JVMs, because the staticFieldBase is a funny "not object", but when passed through reflection it tries to become an Object and fails.

Retrieval of the unsafe is based on how other google open source projects do it, see for example [AbstractFuture](https://github.com/google/guava/blob/d4bd0c5ffa913104283e61aeb2c41bac641af042/guava/src/com/google/common/util/concurrent/AbstractFuture.java#L1340-L1365), [UnsignedBytes](https://github.com/google/guava/blob/6405852bbf453b14d097b8ec3bcae494334b357d/guava/src/com/google/common/primitives/UnsignedBytes.java#L341-L366), and [protobufs](https://github.com/protocolbuffers/protobuf/blob/520c601c99012101c816b6ccc89e8d6fc28fdbb8/java/core/src/main/java/com/google/protobuf/UnsafeUtil.java#L289-L315).

Fixes #1719, and properly fixes #1672.

(Note that this change also bumps the Github Action's bazel version from 4.2.2 to 6.1.2, because somewhere along the way from 4.2.2->6.1.2, Bazel fixed the `--javacopts="--release 8"` option to also allow `sun.misc.Unsafe`.)

PiperOrigin-RevId: 529205332
@copybara-service copybara-service bot closed this in 95a2fbb May 4, 2023
copybara-service bot pushed a commit that referenced this pull request May 4, 2023
…nsafe, and call getObject on it natively instead of through reflection. (Take 2, after exempting its usages internally.)

Calling getObject through reflection breaks Azul JVMs, because the staticFieldBase is a funny "not object", but when passed through reflection it tries to become an Object and fails.

Retrieval of the unsafe is based on how other google open source projects do it, see for example AbstractFuture, UnsignedBytes, and protobufs.

Fixes #1719, and properly fixes #1672.

(Note that this change also bumps the Github Action's bazel version from 4.2.2 to 6.1.2, because somewhere along the way from 4.2.2->6.1.2, Bazel fixed the --javacopts="--release 8" option to also allow sun.misc.Unsafe.)

PiperOrigin-RevId: 529479291
copybara-service bot pushed a commit that referenced this pull request May 4, 2023
…nsafe, and call getObject on it natively instead of through reflection. (Take 2, after exempting its usages internally.)

Calling getObject through reflection breaks Azul JVMs, because the staticFieldBase is a funny "not object", but when passed through reflection it tries to become an Object and fails.

Retrieval of the unsafe is based on how other google open source projects do it, see for example AbstractFuture, UnsignedBytes, and protobufs.

Fixes #1719, and properly fixes #1672.

(Note that this change also bumps the Github Action's bazel version from 4.2.2 to 6.1.2, because somewhere along the way from 4.2.2->6.1.2, Bazel fixed the --javacopts="--release 8" option to also allow sun.misc.Unsafe.)

PiperOrigin-RevId: 529486761
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.

HiddenClassDefiner incorrectly assumes Unsafe.staticFieldBase(Field) returns a real Object
4 participants