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

HiddenClassDefiner incorrectly assumes Unsafe.staticFieldBase(Field) returns a real Object #1672

Closed
breun opened this issue Apr 4, 2023 · 8 comments

Comments

@breun
Copy link

breun commented Apr 4, 2023

The combination of Google Guice 5.1.0 and the Azul Prime JVM is causing a VM crash. I've reached out to Azul support about this and they believe that the root cause is that the class com.google.inject.internal.aop.HiddenClassDefiner treats a klassOop as though it were an Object.

HiddenClassDefiner defines TRUSTED_LOOKUP_BASE like this:

private static final Object TRUSTED_LOOKUP_BASE;

And its value is obtained like this:

Method baseMethod = unsafeType.getMethod("staticFieldBase", Field.class);
TRUSTED_LOOKUP_BASE = baseMethod.invoke(THE_UNSAFE, trustedLookupField);

So TRUSTED_LOOKUP_BASE, nominally a private static final Object, is obtained by calling Unsafe.staticFieldBase() via reflection.

Here's how it's used in Guice:

public Class<?> define(Class<?> hostClass, byte[] bytecode) throws Exception {
    Lookup trustedLookup =
        (Lookup) GET_OBJECT_METHOD.invoke(THE_UNSAFE, TRUSTED_LOOKUP_BASE, TRUSTED_LOOKUP_OFFSET);

Which calls Unsafe.getObject((Object)TRUSTED_LOOKUP_BASE, (long)TRUSTED_LOOKUP_OFFSET).

This use of TRUSTED_LOOKUP_BASE ignores the admonitions of Unsafe.staticFieldBase(), which says the value is not guaranteed to be a real Object:

/**
 * Reports the location of a given static field, in conjunction with {@link
 * #staticFieldOffset}.
 * <p>Fetch the base "Object", if any, with which static fields of the
 * given class can be accessed via methods like {@link #getInt(Object,
 * long)}.  This value may be null.  This value may refer to an object
 * which is a "cookie", not guaranteed to be a real Object, and it should
 * not be used in any way except as argument to the get and put routines in
 * this class.
 */
@ForceInline
public Object staticFieldBase(Field f) {

Now, a lot of reflection code is generated on the fly at runtime and inlined. I'm also told the Prime VM emits a checkcast bytecode, which does what it says and checks the reference to be returned by getObject is, in fact, an Object, which a klassOop isn't. Hence the attempt to throw a ClassCastException in a place where the VM shouldn't.

Here is the hs_err file for the crash: hs_err_pid12.log.gz

@mcculls
Copy link
Contributor

mcculls commented Apr 5, 2023

Hi @breun - I'm not sure I fully agree with that assessment because the code is taking the values returned by staticFieldBase and staticFieldOffset and passing them into unsafe.getObject which is allowed (and intended) according to the Unsafe javadoc:

     * <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.

But I agree that we're not doing that directly though because of the use of the reflective call, and it appears that Azul Prime is doing something different when reflection is involved.

I believe we can simplify that call to avoid using the value from staticFieldBase because we know that field is a member of Lookup.class - according to the javadoc we can use Lookup.class directly, which should hopefully avoid the issue on Azul Prime:

    Lookup trustedLookup =
        (Lookup) GET_OBJECT_METHOD.invoke(THE_UNSAFE, Lookup.class, TRUSTED_LOOKUP_OFFSET);

I'll try to recreate the original issue to verify this fix.

@mcculls
Copy link
Contributor

mcculls commented Apr 7, 2023

@breun PR #1673 has a potential fix - could you build this locally and try it out with your Azul Prime setup?

@breun
Copy link
Author

breun commented Apr 11, 2023

@mcculls I've tried building your branch locally and I believe I need to use Bazel, which I've installed, but I haven't figured out yet what command I need to run to build Guice. Can you tell me how to build Guice or point me to documentation for this?

@mcculls
Copy link
Contributor

mcculls commented Apr 11, 2023 via email

@breun
Copy link
Author

breun commented Apr 12, 2023

I was able to install Guice 5.1.1-SNAPSHOT from your branch, but the next step is building Maven 3.9.1 with this version of Guice, but I various tests fail when building Maven 3.9.1 with Guice 5.1.1-SNAPSHOT due to java.lang.NoSuchMethodError: com.google.common.collect.ImmutableMap$Builder.buildOrThrow()Lcom/google/common/collect/ImmutableMap;. Any ideas? I can build Maven 3.9.1 with Guice 5.1.0 without problems.

@mcculls
Copy link
Contributor

mcculls commented Apr 12, 2023 via email

@azul-jf
Copy link

azul-jf commented Apr 26, 2023

Hi @mcculls we at Azul have a change for this issue which we prefer to the one you proposed. I believe we're waiting for Google to approve the paperwork we've submitted that's required to join the project. Is there someone who can help expedite the application?

@sameb
Copy link
Member

sameb commented Apr 26, 2023

@azul-jf: Please open a pull request with your proposed change and we can discuss it there. The automated CLA checks will kick in when the PR is open. It looks like Azul already has a corp CLA agreement AFAICT, so so long as the author of the PR is a member of Azul's CLA, it should just work.

alsemenov added a commit to alsemenov/guice that referenced this issue May 2, 2023
 * pass result of Unsafe.staticFieldBase() instead of Lookup.class
to conform with specification for Unsafe.getObject() requirements.
 * avoid calling Unsafe.getObject() via reflection

Fixes google#1672
alsemenov added a commit to alsemenov/guice that referenced this issue May 2, 2023
 * pass result of Unsafe.staticFieldBase() instead of Lookup.class
to conform with specification for Unsafe.getObject() requirements.
 * avoid calling Unsafe.getObject() via reflection

Fixes google#1672
alsemenov added a commit to alsemenov/guice that referenced this issue May 3, 2023
 * pass result of Unsafe.staticFieldBase() instead of Lookup.class
to conform with specification for Unsafe.getObject() requirements.
The specification requires the arguments for Unsafe.getObject()
to be obtained from Unsafe.staticFieldBase() and Unsafe.staticFieldOffset().
 * avoid calling Unsafe.getObject() via reflection, because passing
result of Unsafe.staticFieldBase() via reflection might cause problems
if it is not real java object.

Fixes google#1672
copybara-service bot pushed a commit that referenced this issue 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 bot pushed a commit that referenced this issue 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: 529465570
copybara-service bot pushed a commit that referenced this issue 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 issue 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