1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @compile src/Fields.java
  27  * @run testng/othervm UnreflectTest
  28  * @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle
  29  */
  30 
  31 import java.io.IOException;
  32 import java.io.UncheckedIOException;
  33 import java.lang.invoke.MethodHandle;
  34 import java.lang.invoke.MethodHandles;
  35 import java.lang.invoke.VarHandle;
  36 import java.lang.reflect.Field;
  37 import java.lang.reflect.Modifier;
  38 import java.nio.file.Files;
  39 import java.nio.file.Path;
  40 import java.nio.file.Paths;
  41 
  42 import org.testng.annotations.Test;
  43 import static org.testng.Assert.*;
  44 
  45 public class UnreflectTest {
  46     static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
  47     static final Class<?> hiddenClass = defineHiddenClass();
  48     private static Class<?> defineHiddenClass() {
  49         String classes = System.getProperty("test.classes");
  50         Path cf = Paths.get(classes, "Fields.class");
  51         try {
  52             byte[] bytes = Files.readAllBytes(cf);
  53             return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
  54         } catch (IOException e) {
  55             throw new UncheckedIOException(e);
  56         } catch (IllegalAccessException e) {
  57             throw new RuntimeException(e);
  58         }
  59     }
  60 
  61     /*
  62      * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
  63      * can write the value of a non-static final field in a normal class
  64      */
  65     @Test
  66     public void testFieldsInNormalClass() throws Throwable {
  67         // despite the name "HiddenClass", this class is loaded by the
  68         // class loader as non-hidden class
  69         Class<?> c = Fields.class;
  70         Fields o = new Fields();
  71         assertFalse(c.isHiddenClass());
  72         readOnlyAccessibleObject(c, "STATIC_FINAL", null, true);
  73         readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false);
  74         readWriteAccessibleObject(c, "FINAL", o, true);
  75         readWriteAccessibleObject(c, "NON_FINAL", o, false);
  76     }
  77 
  78     /*
  79      * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
  80      * has NO write the value of a non-static final field in a hidden class
  81      */
  82     @Test
  83     public void testFieldsInHiddenClass() throws Throwable {
  84         assertTrue(hiddenClass.isHiddenClass());
  85         Object o = hiddenClass.newInstance();
  86         readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true);
  87         readWriteAccessibleObject(hiddenClass, "STATIC_NON_FINAL", null, false);
  88         readOnlyAccessibleObject(hiddenClass, "FINAL", o, true);
  89         readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false);
  90     }
  91 
  92     /*
  93      * Verify read-only access via unreflectSetter and unreflectVarHandle
  94      */
  95     private static void readOnlyAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Throwable {
  96         Field f = c.getDeclaredField(name);
  97         int modifier = f.getModifiers();
  98         if (isFinal) {
  99             assertTrue(Modifier.isFinal(modifier));
 100         } else {
 101             assertFalse(Modifier.isFinal(modifier));
 102         }
 103         assertTrue(f.trySetAccessible());
 104 
 105         // Field object with read-only access
 106         MethodHandle mh = LOOKUP.unreflectGetter(f);
 107         Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
 108         assertTrue(value == f.get(o));
 109         try {
 110             LOOKUP.unreflectSetter(f);
 111             assertTrue(false, "should fail to unreflect a setter for " + name);
 112         } catch (IllegalAccessException e) {
 113         }
 114 
 115         VarHandle vh = LOOKUP.unreflectVarHandle(f);
 116         if (isFinal) {
 117             assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
 118         } else {
 119             assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
 120         }
 121     }
 122 
 123     private static void readWriteAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Throwable {
 124         Field f = c.getDeclaredField(name);
 125         int modifier = f.getModifiers();
 126         if (isFinal) {
 127             assertTrue(Modifier.isFinal(modifier));
 128         } else {
 129             assertFalse(Modifier.isFinal(modifier));
 130         }
 131         assertTrue(f.trySetAccessible());
 132 
 133         // Field object with read-write access
 134         MethodHandle mh = MethodHandles.lookup().unreflectGetter(f);
 135         Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
 136         assertTrue(value == f.get(o));
 137         try {
 138             MethodHandle setter = MethodHandles.lookup().unreflectSetter(f);
 139             if (Modifier.isStatic(modifier)) {
 140                 setter.invokeExact(value);
 141             } else {
 142                 setter.invoke(o, value);
 143             }
 144         } catch (IllegalAccessException e) {
 145             throw e;
 146         }
 147 
 148         VarHandle vh = LOOKUP.unreflectVarHandle(f);
 149         if (isFinal) {
 150             assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
 151         } else {
 152             assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
 153         }
 154     }
 155 }