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  * @bug 8238358 8247444
27  * @run testng/othervm UnreflectTest
28  * @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle on
29  *          trusted final fields (declared in hidden classes and records)
30  */
31 
32 import java.io.IOException;
33 import java.io.UncheckedIOException;
34 import java.lang.invoke.MethodHandle;
35 import java.lang.invoke.MethodHandles;
36 import java.lang.invoke.VarHandle;
37 import java.lang.reflect.Field;
38 import java.lang.reflect.Modifier;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.nio.file.Paths;
42 
43 import org.testng.annotations.Test;
44 import static org.testng.Assert.*;
45 
46 public class UnreflectTest {
47     static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
48     static final Class<?> hiddenClass = defineHiddenClass();
defineHiddenClass()49     private static Class<?> defineHiddenClass() {
50         String classes = System.getProperty("test.classes");
51         Path cf = Paths.get(classes, "Fields.class");
52         try {
53             byte[] bytes = Files.readAllBytes(cf);
54             return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass();
55         } catch (IOException e) {
56             throw new UncheckedIOException(e);
57         } catch (IllegalAccessException e) {
58             throw new RuntimeException(e);
59         }
60     }
61 
62     /*
63      * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
64      * can write the value of a non-static final field in a normal class
65      */
66     @Test
testFieldsInNormalClass()67     public void testFieldsInNormalClass() throws Throwable {
68         // despite the name "HiddenClass", this class is loaded by the
69         // class loader as non-hidden class
70         Class<?> c = Fields.class;
71         Fields o = new Fields();
72         assertFalse(c.isHidden());
73         readOnlyAccessibleObject(c, "STATIC_FINAL", null, true);
74         readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false);
75         readWriteAccessibleObject(c, "FINAL", o, true);
76         readWriteAccessibleObject(c, "NON_FINAL", o, false);
77     }
78 
79     /*
80      * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
81      * has NO write the value of a non-static final field in a hidden class
82      */
83     @Test
testFieldsInHiddenClass()84     public void testFieldsInHiddenClass() throws Throwable {
85         assertTrue(hiddenClass.isHidden());
86         Object o = hiddenClass.newInstance();
87         readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true);
88         readWriteAccessibleObject(hiddenClass, "STATIC_NON_FINAL", null, false);
89         readOnlyAccessibleObject(hiddenClass, "FINAL", o, true);
90         readWriteAccessibleObject(hiddenClass, "NON_FINAL", o, false);
91     }
92 
TestRecord(int i)93     static record TestRecord(int i) {
94         static final Object STATIC_FINAL = new Object();
95         static Object STATIC_NON_FINAL = new Object();
96     }
97 
98     /*
99      * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that
100      * cannot write the value of a non-static final field in a record class
101      */
testFieldsInRecordClass()102     public void testFieldsInRecordClass() throws Throwable {
103         assertTrue(TestRecord.class.isRecord());
104         Object o = new TestRecord(1);
105         readOnlyAccessibleObject(TestRecord.class, "STATIC_FINAL", null, true);
106         readWriteAccessibleObject(TestRecord.class, "STATIC_NON_FINAL", null, false);
107         readOnlyAccessibleObject(TestRecord.class, "i", o, true);
108     }
109 
110     /*
111      * Verify read-only access via unreflectSetter and unreflectVarHandle
112      */
readOnlyAccessibleObject(Class<?> c, String name, Object o, boolean isFinal)113     private static void readOnlyAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Throwable {
114         Field f = c.getDeclaredField(name);
115         int modifier = f.getModifiers();
116         if (isFinal) {
117             assertTrue(Modifier.isFinal(modifier));
118         } else {
119             assertFalse(Modifier.isFinal(modifier));
120         }
121         assertTrue(f.trySetAccessible());
122 
123         // Field object with read-only access
124         MethodHandle mh = LOOKUP.unreflectGetter(f);
125         Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
126         assertTrue(value == f.get(o));
127         try {
128             LOOKUP.unreflectSetter(f);
129             assertTrue(false, "should fail to unreflect a setter for " + name);
130         } catch (IllegalAccessException e) {
131         }
132 
133         VarHandle vh = LOOKUP.unreflectVarHandle(f);
134         if (isFinal) {
135             assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
136         } else {
137             assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
138         }
139     }
140 
readWriteAccessibleObject(Class<?> c, String name, Object o, boolean isFinal)141     private static void readWriteAccessibleObject(Class<?> c, String name, Object o, boolean isFinal) throws Throwable {
142         Field f = c.getDeclaredField(name);
143         int modifier = f.getModifiers();
144         if (isFinal) {
145             assertTrue(Modifier.isFinal(modifier));
146         } else {
147             assertFalse(Modifier.isFinal(modifier));
148         }
149         assertTrue(f.trySetAccessible());
150 
151         // Field object with read-write access
152         MethodHandle mh = MethodHandles.lookup().unreflectGetter(f);
153         Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o);
154         assertTrue(value == f.get(o));
155         try {
156             MethodHandle setter = MethodHandles.lookup().unreflectSetter(f);
157             if (Modifier.isStatic(modifier)) {
158                 setter.invokeExact(value);
159             } else {
160                 setter.invoke(o, value);
161             }
162         } catch (IllegalAccessException e) {
163             throw e;
164         }
165 
166         VarHandle vh = LOOKUP.unreflectVarHandle(f);
167         if (isFinal) {
168             assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
169         } else {
170             assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET));
171         }
172     }
173 }
174