changeset 58785:fdd1e98db1a6 nestmates

8235602: Re-examine if a hidden class should trust final non static fields Reviewed-by: psandoz, chegar, forax
author mchung
date Thu, 30 Jan 2020 11:38:07 -0800
parents b2325720e6e2
children ada29ed7a804
files src/java.base/share/classes/java/lang/invoke/MethodHandles.java src/java.base/share/classes/java/lang/reflect/AccessibleObject.java src/java.base/share/classes/java/lang/reflect/Field.java src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java src/jdk.unsupported/share/classes/sun/misc/Unsafe.java test/jdk/java/lang/invoke/defineHiddenClass/UnreflectTest.java test/jdk/java/lang/invoke/defineHiddenClass/src/Fields.java test/jdk/java/lang/reflect/AccessibleObject/Fields.java test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java test/jdk/sun/misc/UnsafeFieldOffsets.java
diffstat 10 files changed, 513 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Fri Jan 17 12:03:22 2020 -0800
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Thu Jan 30 11:38:07 2020 -0800
@@ -2961,8 +2961,13 @@
 
         private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException {
             MemberName field = new MemberName(f, isSetter);
-            if (isSetter && field.isStatic() && field.isFinal())
-                throw field.makeAccessException("static final field has no write access", this);
+            if (isSetter && field.isFinal()) {
+                if (field.isStatic()) {
+                    throw field.makeAccessException("static final field has no write access", this);
+                } else if (field.getDeclaringClass().isHiddenClass()){
+                    throw field.makeAccessException("final field in a hidden class has no write access", this);
+                }
+            }
             assert(isSetter
                     ? MethodHandleNatives.refKindIsSetter(field.getReferenceKind())
                     : MethodHandleNatives.refKindIsGetter(field.getReferenceKind()));
@@ -3523,7 +3528,8 @@
                 }
                 refc = lookupClass();
             }
-            return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(), this.allowedModes == TRUSTED);
+            return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(),
+                                             this.allowedModes == TRUSTED && !getField.getDeclaringClass().isHiddenClass());
         }
         /** Check access and get the requested constructor. */
         private MethodHandle getDirectConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
--- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java	Fri Jan 17 12:03:22 2020 -0800
+++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java	Thu Jan 30 11:38:07 2020 -0800
@@ -176,6 +176,12 @@
      * to the caller and the package containing the declaring class is not open
      * to the caller's module. </p>
      *
+     * <p> This method cannot be used to enable {@linkplain Field#set <em>write</em>}
+     * access to a final field declared in a {@linkplain Class#isHiddenClass() hidden class},
+     * since such fields are not modifiable.  The {@code accessible} flag when
+     * {@code true} suppresses Java language access control checks to only
+     * enable {@linkplain Field#get <em>read</em>} access to such fields.
+     *
      * <p> If there is a security manager, its
      * {@code checkPermission} method is first called with a
      * {@code ReflectPermission("suppressAccessChecks")} permission.
--- a/src/java.base/share/classes/java/lang/reflect/Field.java	Fri Jan 17 12:03:22 2020 -0800
+++ b/src/java.base/share/classes/java/lang/reflect/Field.java	Thu Jan 30 11:38:07 2020 -0800
@@ -721,10 +721,19 @@
      * the underlying field is inaccessible, the method throws an
      * {@code IllegalAccessException}.
      *
-     * <p>If the underlying field is final, the method throws an
-     * {@code IllegalAccessException} unless {@code setAccessible(true)}
-     * has succeeded for this {@code Field} object
-     * and the field is non-static. Setting a final field in this way
+     * <p>If the underlying field is final, this {@code Field} object has
+     * <em>write</em> access if and only if the following conditions are met:
+     * <ul>
+     * <li>{@link #setAccessible(boolean) setAccessible(true)} has succeeded for
+     *     this {@code Field} object;</li>
+     * <li>the field is non-static; and</li>
+     * <li>the field's declaring class is not a {@linkplain Class#isHiddenClass()
+     *     hidden class}.</li>
+     * </ul>
+     * If any of the above checks is not met, this method throws an
+     * {@code IllegalAccessException}.
+     *
+     * <p> Setting a final field in this way
      * is meaningful only during deserialization or reconstruction of
      * instances of classes with blank final fields, before they are
      * made available for access by other parts of a program. Use in
@@ -756,7 +765,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -791,7 +801,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -827,7 +838,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -863,7 +875,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -899,7 +912,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -935,7 +949,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -971,7 +986,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -1007,7 +1023,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
@@ -1043,7 +1060,8 @@
      *
      * @throws    IllegalAccessException    if this {@code Field} object
      *              is enforcing Java language access control and the underlying
-     *              field is either inaccessible or final.
+     *              field is either inaccessible or final;
+     *              or if this {@code Field} object has no write access.
      * @throws    IllegalArgumentException  if the specified object is not an
      *              instance of the class or interface declaring the underlying
      *              field (or a subclass or implementor thereof),
--- a/src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java	Fri Jan 17 12:03:22 2020 -0800
+++ b/src/java.base/share/classes/jdk/internal/reflect/UnsafeFieldAccessorFactory.java	Thu Jan 30 11:38:07 2020 -0800
@@ -35,7 +35,7 @@
         boolean isFinal = Modifier.isFinal(field.getModifiers());
         boolean isVolatile = Modifier.isVolatile(field.getModifiers());
         boolean isQualified = isFinal || isVolatile;
-        boolean isReadOnly = isFinal && (isStatic || !override);
+        boolean isReadOnly = isFinal && (isStatic || !override || field.getDeclaringClass().isHiddenClass());
         if (isStatic) {
             // This code path does not guarantee that the field's
             // declaring class has been initialized, but it must be
--- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java	Fri Jan 17 12:03:22 2020 -0800
+++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java	Thu Jan 30 11:38:07 2020 -0800
@@ -637,6 +637,12 @@
      */
     @ForceInline
     public long objectFieldOffset(Field f) {
+        if (f == null) {
+            throw new NullPointerException();
+        }
+        if (f.getDeclaringClass().isHiddenClass()) {
+            throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
+        }
         return theInternalUnsafe.objectFieldOffset(f);
     }
 
@@ -659,6 +665,12 @@
      */
     @ForceInline
     public long staticFieldOffset(Field f) {
+        if (f == null) {
+            throw new NullPointerException();
+        }
+        if (f.getDeclaringClass().isHiddenClass()) {
+            throw new UnsupportedOperationException("can't get field offset on a hidden class: " + f);
+        }
         return theInternalUnsafe.staticFieldOffset(f);
     }
 
@@ -674,6 +686,12 @@
      */
     @ForceInline
     public Object staticFieldBase(Field f) {
+        if (f == null) {
+            throw new NullPointerException();
+        }
+        if (f.getDeclaringClass().isHiddenClass()) {
+            throw new UnsupportedOperationException("can't get base address on a hidden class: " + f);
+        }
         return theInternalUnsafe.staticFieldBase(f);
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/defineHiddenClass/UnreflectTest.java	Thu Jan 30 11:38:07 2020 -0800
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @compile src/Fields.java
+ * @run testng/othervm UnreflectTest
+ * @summary Test java.lang.reflect.AccessibleObject with modules
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+public class UnreflectTest {
+    static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+    static final Class<?> hiddenClass = defineHiddenClass();
+    private static Class<?> defineHiddenClass() {
+        String classes = System.getProperty("test.classes");
+        Path cf = Paths.get(classes, "Fields.class");
+        try {
+            byte[] bytes = Files.readAllBytes(cf);
+            return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void testNonHiddenFields() throws Throwable {
+        // despite the name "HiddenClass", this class is loaded by the
+        // class loader as non-hidden class
+        Class<?> c = Fields.class;
+        Fields o = new Fields();
+        assertFalse(c.isHiddenClass());
+        readOnlyAccessibleObject(c, "STATIC_FINAL", null, true);
+        readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false);
+        readWriteAccessibleObject(c, "FINAL", o, true);
+        readWriteAccessibleObject(c, "NON_FINAL", o, false);
+    }
+
+    @Test
+    public void testHiddenFields() throws Throwable {
+        assertTrue(hiddenClass.isHiddenClass());
+        Object o = hiddenClass.newInstance();
+        readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true);
+        readWriteAccessibleObject(hiddenClass, "STATIC_NON_FINAL", null, false);
+        readOnlyAccessibleObject(hiddenClass, "FINAL", o, true);
+        readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false);
+    }
+
+    /*
+     * Verify read-only access via unreflectSetter and unreflectVarHandle
+     */
+    private static void readOnlyAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Throwable {
+        Field f = c.getDeclaredField(name);
+        int modifier = f.getModifiers();
+        if (isFinal) {
+            assertTrue(Modifier.isFinal(modifier));
+        } else {
+            assertFalse(Modifier.isFinal(modifier));
+        }
+        assertTrue(f.trySetAccessible());
+
+        // Field object with read-only access
+        MethodHandle mh = LOOKUP.unreflectGetter(f);
+        Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
+        assertTrue(value == f.get(o));
+        try {
+            LOOKUP.unreflectSetter(f);
+            assertTrue(false, "should fail to unreflect a setter for " + name);
+        } catch (IllegalAccessException e) {
+        }
+
+        VarHandle vh = LOOKUP.unreflectVarHandle(f);
+        if (isFinal) {
+            assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
+        } else {
+            assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
+        }
+    }
+
+    private static void readWriteAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Throwable {
+        Field f = c.getDeclaredField(name);
+        int modifier = f.getModifiers();
+        if (isFinal) {
+            assertTrue(Modifier.isFinal(modifier));
+        } else {
+            assertFalse(Modifier.isFinal(modifier));
+        }
+        assertTrue(f.trySetAccessible());
+
+        // Field object with read-write access
+        MethodHandle mh = MethodHandles.lookup().unreflectGetter(f);
+        Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
+        assertTrue(value == f.get(o));
+        try {
+            MethodHandle setter = MethodHandles.lookup().unreflectSetter(f);
+            if (Modifier.isStatic(modifier)) {
+                setter.invokeExact(value);
+            } else {
+                setter.invoke(o, value);
+            }
+        } catch (IllegalAccessException e) {
+            throw e;
+        }
+
+        VarHandle vh = LOOKUP.unreflectVarHandle(f);
+        if (isFinal) {
+            assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
+        } else {
+            assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/invoke/defineHiddenClass/src/Fields.java	Thu Jan 30 11:38:07 2020 -0800
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+public class Fields {
+    static final Object STATIC_FINAL = new Object();
+    static Object STATIC_NON_FINAL = new Object();
+    final Object FINAL = new Object();
+    Object NON_FINAL = new Object();
+
+    public String name() {
+        return this.getClass().getName();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/reflect/AccessibleObject/Fields.java	Thu Jan 30 11:38:07 2020 -0800
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+public class Fields {
+    private static final Object STATIC_FINAL = new Object();
+    private static Object STATIC_NON_FINAL = new Object();
+    private final Object FINAL = new Object();
+    private Object NON_FINAL = new Object();
+
+    public String name() {
+        return this.getClass().getName();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java	Thu Jan 30 11:38:07 2020 -0800
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @build Fields HiddenClassTest
+ * @run testng/othervm HiddenClassTest
+ * @summary Test java.lang.reflect.AccessibleObject with modules
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+public class HiddenClassTest {
+    static final Class<?> hiddenClass = defineHiddenClass();
+    private static Class<?> defineHiddenClass() {
+        String classes = System.getProperty("test.classes");
+        Path cf = Paths.get(classes, "Fields.class");
+        try {
+            byte[] bytes = Files.readAllBytes(cf);
+            return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void testNonHiddenFields() throws Exception {
+        // despite the name "HiddenClass", this class is loaded by the
+        // class loader as non-hidden class
+        Class<?> c = Fields.class;
+        Fields o = new Fields();
+        assertFalse(c.isHiddenClass());
+        readOnlyAccessibleObject(c, "STATIC_FINAL", null, true);
+        readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false);
+        readWriteAccessibleObject(c, "FINAL", o, true);
+        readWriteAccessibleObject(c, "NON_FINAL", o, false);
+    }
+
+    @Test
+    public void testHiddenFields() throws Exception {
+        assertTrue(hiddenClass.isHiddenClass());
+        Object o = hiddenClass.newInstance();
+        readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true);
+        readWriteAccessibleObject(hiddenClass, "STATIC_NON_FINAL", null, false);
+        readOnlyAccessibleObject(hiddenClass, "FINAL", o, true);
+        readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false);
+    }
+
+    private static void readOnlyAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Exception {
+        Field f = c.getDeclaredField(name);
+        int modifier = f.getModifiers();
+        if (isFinal) {
+            assertTrue(Modifier.isFinal(modifier));
+        } else {
+            assertFalse(Modifier.isFinal(modifier));
+        }
+        assertTrue(f.trySetAccessible());
+        assertTrue(f.get(o) != null);
+        try {
+            f.set(o, null);
+            assertTrue(false, "should fail to set " + name);
+        } catch (IllegalAccessException e) {
+        }
+    }
+
+    private static void readWriteAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Exception {
+        Field f = c.getDeclaredField(name);
+        int modifier = f.getModifiers();
+        if (isFinal) {
+            assertTrue(Modifier.isFinal(modifier));
+        } else {
+            assertFalse(Modifier.isFinal(modifier));
+        }
+        assertTrue(f.trySetAccessible());
+        assertTrue(f.get(o) != null);
+        try {
+            f.set(o, null);
+        } catch (IllegalAccessException e) {
+            throw e;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/misc/UnsafeFieldOffsets.java	Thu Jan 30 11:38:07 2020 -0800
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @summary Ensure that sun.misc.Unsafe::objectFieldOffset and staticFieldOffset
+ *          throw UnsupportedOperationException on Field of a hidden class
+ * @modules jdk.unsupported
+ * @run main UnsafeFieldOffsets
+ */
+
+import sun.misc.Unsafe;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class UnsafeFieldOffsets {
+    static class Fields {
+        static final Object STATIC_FINAL = new Object();
+        static Object STATIC_NON_FINAL = new Object();
+        final Object FINAL = new Object();
+        Object NON_FINAL = new Object();
+    }
+
+    private static Unsafe UNSAFE = getUnsafe();
+    private static final Class<?> HIDDEN_CLASS = defineHiddenClass();
+
+    private static Unsafe getUnsafe() {
+        try {
+            Field f = Unsafe.class.getDeclaredField("theUnsafe");
+            f.setAccessible(true);
+            return (Unsafe) f.get(null);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Class<?> defineHiddenClass() {
+        String classes = System.getProperty("test.classes");
+        Path cf = Paths.get(classes, "UnsafeFieldOffsets$Fields.class");
+        try {
+            byte[] bytes = Files.readAllBytes(cf);
+            Class<?> c = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
+            if (!c.isHiddenClass())
+                throw new RuntimeException("Expected hidden class: " + c);
+            return c;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        // non-hidden class
+        testStaticField(Fields.class, "STATIC_FINAL");
+        testStaticField(Fields.class, "STATIC_NON_FINAL");
+        testInstanceField(Fields.class, "FINAL");
+        testInstanceField(Fields.class, "NON_FINAL");
+
+        // hidden class
+        testStaticField(HIDDEN_CLASS, "STATIC_FINAL");
+        testStaticField(HIDDEN_CLASS, "STATIC_NON_FINAL");
+        testInstanceField(HIDDEN_CLASS, "FINAL");
+        testInstanceField(HIDDEN_CLASS, "NON_FINAL");
+    }
+
+    private static void testStaticField(Class<?> c, String name) throws Exception {
+        Field f = c.getDeclaredField(name);
+        try {
+            UNSAFE.staticFieldOffset(f);
+            assertNonHiddenClass(c);
+        } catch (UnsupportedOperationException e) {
+            assertHiddenClass(c);
+        }
+    }
+
+    private static void testInstanceField(Class<?> c, String name) throws Exception {
+        Field f = c.getDeclaredField(name);
+        try {
+            UNSAFE.objectFieldOffset(f);
+            assertNonHiddenClass(c);
+        } catch (UnsupportedOperationException e) {
+            assertHiddenClass(c);
+        }
+    }
+
+    private static void assertNonHiddenClass(Class<?> c) {
+        if (c.isHiddenClass())
+            throw new RuntimeException("Expected UOE but not thrown: " + c);
+    }
+
+    private static void assertHiddenClass(Class<?> c) {
+        if (!c.isHiddenClass())
+            throw new RuntimeException("Expected hidden class but is not: " + c);
+    }
+}