1 /*
  2  * Copyright (c) 2018, 2021, 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 /* @test
 25  * @bug 8196830 8235351 8257874
 26  * @modules java.base/jdk.internal.reflect
 27  * @run testng/othervm CallerSensitiveAccess
 28  * @summary Check Lookup findVirtual, findStatic and unreflect behavior with
 29  *          caller sensitive methods with focus on AccessibleObject.setAccessible
 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.MethodHandles.Lookup;
 37 import java.lang.invoke.MethodType;
 38 import java.lang.module.ModuleReader;
 39 import java.lang.module.ModuleReference;
 40 import java.lang.reflect.AccessibleObject;
 41 import java.lang.reflect.Field;
 42 import java.lang.reflect.InaccessibleObjectException;
 43 import java.lang.reflect.Method;
 44 import java.lang.reflect.Modifier;
 45 import java.util.Arrays;
 46 import java.util.List;
 47 import java.util.StringJoiner;
 48 import java.util.stream.Collectors;
 49 import java.util.stream.Stream;
 50 
 51 import jdk.internal.reflect.CallerSensitive;
 52 
 53 import org.testng.annotations.DataProvider;
 54 import org.testng.annotations.NoInjection;
 55 import org.testng.annotations.Test;
 56 import static org.testng.Assert.*;
 57 
 58 public class CallerSensitiveAccess {
 59 
 60     /**
 61      * Caller sensitive methods in APIs exported by java.base.
 62      */
 63     @DataProvider(name = "callerSensitiveMethods")
 64     static Object[][] callerSensitiveMethods() {
 65         try (Stream<Method> stream = callerSensitiveMethods(Object.class.getModule())) {
 66             return stream.map(m -> new Object[]{m, shortDescription(m)})
 67                     .toArray(Object[][]::new);
 68         }
 69     }
 70 
 71     /**
 72      * Using publicLookup, attempt to use findVirtual or findStatic to obtain a
 73      * method handle to a caller sensitive method.
 74      */
 75     @Test(dataProvider = "callerSensitiveMethods",
 76             expectedExceptions = IllegalAccessException.class)
 77     public void testPublicLookupFind(@NoInjection Method method, String desc) throws Exception {
 78         Lookup lookup = MethodHandles.publicLookup();
 79         Class<?> refc = method.getDeclaringClass();
 80         String name = method.getName();
 81         MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
 82         if (Modifier.isStatic(method.getModifiers())) {
 83             lookup.findStatic(refc, name, mt);
 84         } else {
 85             lookup.findVirtual(refc, name, mt);
 86         }
 87     }
 88 
 89     /**
 90      * Using publicLookup, attempt to use unreflect to obtain a method handle to a
 91      * caller sensitive method.
 92      */
 93     @Test(dataProvider = "callerSensitiveMethods",
 94             expectedExceptions = IllegalAccessException.class)
 95     public void testPublicLookupUnreflect(@NoInjection Method method, String desc) throws Exception {
 96         MethodHandles.publicLookup().unreflect(method);
 97     }
 98 
 99     /**
100      * public accessible caller sensitive methods in APIs exported by java.base.
101      */
102     @DataProvider(name = "accessibleCallerSensitiveMethods")
103     static Object[][] accessibleCallerSensitiveMethods() {
104         try (Stream<Method> stream = callerSensitiveMethods(Object.class.getModule())) {
105             return stream
106                 .filter(m -> Modifier.isPublic(m.getModifiers()))
107                 .map(m -> { m.setAccessible(true); return m; })
108                 .map(m -> new Object[] { m, shortDescription(m) })
109                 .toArray(Object[][]::new);
110         }
111     }
112 
113     /**
114      * Using publicLookup, attempt to use unreflect to obtain a method handle to a
115      * caller sensitive method.
116      */
117     @Test(dataProvider = "accessibleCallerSensitiveMethods",
118             expectedExceptions = IllegalAccessException.class)
119     public void testLookupUnreflect(@NoInjection Method method, String desc) throws Exception {
120         MethodHandles.publicLookup().unreflect(method);
121     }
122 
123     /**
124      * Using a Lookup with no original access that can't lookup caller-sensitive
125      * method
126      */
127     @Test(dataProvider = "callerSensitiveMethods",
128             expectedExceptions = IllegalAccessException.class)
129     public void testLookupNoOriginalAccessFind(@NoInjection Method method, String desc) throws Exception {
130         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL);
131         assertTrue(lookup.hasFullPrivilegeAccess());
132         Class<?> refc = method.getDeclaringClass();
133         String name = method.getName();
134         MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
135         if (Modifier.isStatic(method.getModifiers())) {
136             lookup.findStatic(refc, name, mt);
137         } else {
138             lookup.findVirtual(refc, name, mt);
139         }
140     }
141 
142     /**
143      * Using a Lookup with no original access that can't unreflect caller-sensitive
144      * method
145      */
146     @Test(dataProvider = "callerSensitiveMethods",
147             expectedExceptions = IllegalAccessException.class)
148     public void testLookupNoOriginalAccessUnreflect(@NoInjection Method method, String desc) throws Exception {
149         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL);
150         assertTrue(lookup.hasFullPrivilegeAccess());
151         lookup.unreflect(method);
152     }
153 
154     // -- Test method handles to setAccessible --
155 
156     private int aField;
157 
158     Field accessibleField() {
159         try {
160             return getClass().getDeclaredField("aField");
161         } catch (NoSuchFieldException e) {
162             fail();
163             return null;
164         }
165     }
166 
167     Field inaccessibleField() {
168         try {
169             return String.class.getDeclaredField("hash");
170         } catch (NoSuchFieldException e) {
171             fail();
172             return null;
173         }
174     }
175 
176     void findAndInvokeSetAccessible(Class<? extends AccessibleObject> refc, Field f) throws Throwable {
177         MethodType mt = MethodType.methodType(void.class, boolean.class);
178         MethodHandle mh = MethodHandles.lookup().findVirtual(refc, "setAccessible", mt);
179         mh.invoke(f, true);  // may throw InaccessibleObjectException
180         assertTrue(f.isAccessible());
181     }
182 
183     void unreflectAndInvokeSetAccessible(Method m, Field f) throws Throwable {
184         assertTrue(m.getName().equals("setAccessible"));
185         assertFalse(Modifier.isStatic(m.getModifiers()));
186         MethodHandle mh = MethodHandles.lookup().unreflect(m);
187         mh.invoke(f, true);   // may throw InaccessibleObjectException
188         assertTrue(f.isAccessible());
189     }
190 
191     /**
192      * Create a method handle to setAccessible and use it to suppress access to an
193      * accessible member.
194      */
195     @Test
196     public void testSetAccessible1() throws Throwable {
197         findAndInvokeSetAccessible(AccessibleObject.class, accessibleField());
198     }
199     @Test
200     public void testSetAccessible2() throws Throwable {
201         findAndInvokeSetAccessible(Field.class, accessibleField());
202     }
203     @Test
204     public void testSetAccessible3() throws Throwable {
205         Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class);
206         unreflectAndInvokeSetAccessible(m, accessibleField());
207     }
208     @Test
209     public void testSetAccessible4() throws Throwable {
210         Method m = Field.class.getMethod("setAccessible", boolean.class);
211         unreflectAndInvokeSetAccessible(m, accessibleField());
212     }
213 
214     /**
215      * Create a method handle to setAccessible and attempt to use it to suppress
216      * access to an inaccessible member.
217      */
218     @Test(expectedExceptions = InaccessibleObjectException.class)
219     public void testSetAccessible5() throws Throwable {
220         findAndInvokeSetAccessible(AccessibleObject.class, inaccessibleField());
221     }
222     @Test(expectedExceptions = InaccessibleObjectException.class)
223     public void testSetAccessible6() throws Throwable {
224         findAndInvokeSetAccessible(Field.class, inaccessibleField());
225     }
226     @Test(expectedExceptions = InaccessibleObjectException.class)
227     public void testSetAccessible7() throws Throwable {
228         Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class);
229         unreflectAndInvokeSetAccessible(m, inaccessibleField());
230     }
231     @Test(expectedExceptions = InaccessibleObjectException.class)
232     public void testSetAccessible8() throws Throwable {
233         Method m = Field.class.getMethod("setAccessible", boolean.class);
234         unreflectAndInvokeSetAccessible(m, inaccessibleField());
235     }
236 
237 
238     // -- Test sub-classes of AccessibleObject --
239 
240     /**
241      * Custom AccessibleObject objects. One class overrides setAccessible, the other
242      * does not override this method.
243      */
244     @DataProvider(name = "customAccessibleObjects")
245     static Object[][] customAccessibleObjectClasses() {
246         return new Object[][] { { new S1() }, { new S2() } };
247     }
248     public static class S1 extends AccessibleObject { }
249     public static class S2 extends AccessibleObject {
250         @Override
251         public void setAccessible(boolean flag) {
252             super.setAccessible(flag);
253         }
254     }
255 
256     void findAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable {
257         MethodType mt = MethodType.methodType(void.class, boolean.class);
258         MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt);
259         mh.invoke(obj, true);
260         assertTrue(obj.isAccessible());
261     }
262 
263     void unreflectAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable {
264         Method m = obj.getClass().getMethod("setAccessible", boolean.class);
265         MethodHandle mh = lookup.unreflect(m);
266         mh.invoke(obj, true);
267         assertTrue(obj.isAccessible());
268     }
269 
270     /**
271      * Using publicLookup, create a method handle to setAccessible and invoke it
272      * on a custom AccessibleObject object.
273      */
274     @Test(expectedExceptions = IllegalAccessException.class)
275     public void testPublicLookupSubclass1() throws Throwable {
276         // S1 does not override setAccessible
277         findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1());
278     }
279     @Test
280     public void testPublicLookupSubclass2() throws Throwable {
281         // S2 overrides setAccessible
282         findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2());
283     }
284     @Test(expectedExceptions = IllegalAccessException.class)
285     public void testPublicLookupSubclass3() throws Throwable {
286         // S1 does not override setAccessible
287         unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1());
288     }
289     @Test
290     public void testPublicLookupSubclass4() throws Throwable {
291         // S2 overrides setAccessible
292         unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2());
293     }
294 
295     /**
296      * Using a full power lookup, create a method handle to setAccessible and
297      * invoke it on a custom AccessibleObject object.
298      */
299     @Test(dataProvider = "customAccessibleObjects")
300     public void testLookupSubclass1(AccessibleObject obj) throws Throwable {
301         findAndInvokeSetAccessible(MethodHandles.lookup(), obj);
302     }
303     @Test(dataProvider = "customAccessibleObjects")
304     public void testLookupSubclass2(AccessibleObject obj) throws Throwable {
305         unreflectAndInvokeSetAccessible(MethodHandles.lookup(), obj);
306     }
307 
308     /**
309      * Using a full power lookup, create a method handle to setAccessible on a
310      * sub-class of AccessibleObject and then attempt to invoke it on a Field object.
311      */
312     @Test(dataProvider = "customAccessibleObjects",
313             expectedExceptions = ClassCastException.class)
314     public void testLookupSubclass3(AccessibleObject obj) throws Throwable {
315         MethodType mt = MethodType.methodType(void.class, boolean.class);
316         Lookup lookup = MethodHandles.lookup();
317         MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt);
318         mh.invoke(accessibleField(), true);  // should throw ClassCastException
319     }
320 
321     /**
322      * Using a full power lookup, use unreflect to create a method handle to
323      * setAccessible on a sub-class of AccessibleObject, then attempt to invoke
324      * it on a Field object.
325      */
326     @Test
327     public void testLookupSubclass4() throws Throwable {
328         // S1 does not override setAccessible
329         Method m = S1.class.getMethod("setAccessible", boolean.class);
330         assertTrue(m.getDeclaringClass() == AccessibleObject.class);
331         MethodHandle mh = MethodHandles.lookup().unreflect(m);
332         Field f = accessibleField();
333         mh.invoke(f, true);
334         assertTrue(f.isAccessible());
335     }
336     @Test(expectedExceptions = InaccessibleObjectException.class)
337     public void testLookupSubclass5() throws Throwable {
338         // S1 does not override setAccessible
339         Method m = S1.class.getMethod("setAccessible", boolean.class);
340         assertTrue(m.getDeclaringClass() == AccessibleObject.class);
341         MethodHandle mh = MethodHandles.lookup().unreflect(m);
342         mh.invoke(inaccessibleField(), true);  // should throw InaccessibleObjectException
343     }
344     @Test(expectedExceptions = ClassCastException.class)
345     public void testLookupSubclass6() throws Throwable {
346         // S2 overrides setAccessible
347         Method m = S2.class.getMethod("setAccessible", boolean.class);
348         assertTrue(m.getDeclaringClass() == S2.class);
349         MethodHandle mh = MethodHandles.lookup().unreflect(m);
350         mh.invoke(accessibleField(), true);  // should throw ClassCastException
351     }
352     @Test(expectedExceptions = ClassCastException.class)
353     public void testLookupSubclass7() throws Throwable {
354         // S2 overrides setAccessible
355         Method m = S2.class.getMethod("setAccessible", boolean.class);
356         assertTrue(m.getDeclaringClass() == S2.class);
357         MethodHandle mh = MethodHandles.lookup().unreflect(m);
358         mh.invoke(inaccessibleField(), true);  // should throw ClassCastException
359     }
360 
361     /**
362      * Field::getInt and Field::setInt are caller-sensitive methods.
363      * Test access to private field.
364      */
365     private static int privateField = 0;
366     @Test
367     public void testPrivateField() throws Throwable {
368         Field f = CallerSensitiveAccess.class.getDeclaredField("privateField");
369         // Field::setInt
370         MethodType mtype = MethodType.methodType(void.class, Object.class, int.class);
371         MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setInt", mtype);
372         mh.invokeExact(f, (Object)null, 5);
373         // Field::getInt
374         mh = MethodHandles.lookup().findVirtual(Field.class, "getInt", MethodType.methodType(int.class, Object.class));
375         int value = (int)mh.invokeExact(f, (Object)null);
376         assertTrue(value == 5);
377     }
378 
379     private static class Inner {
380         private static boolean privateField = false;
381     }
382 
383     @Test
384     public void testInnerPrivateField() throws Throwable {
385         Field f = Inner.class.getDeclaredField("privateField");
386         // Field::setInt
387         MethodType mtype = MethodType.methodType(void.class, Object.class, boolean.class);
388         MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setBoolean", mtype);
389         mh.invokeExact(f, (Object)null, true);
390         // Field::getInt
391         mh = MethodHandles.lookup().findVirtual(Field.class, "getBoolean", MethodType.methodType(boolean.class, Object.class));
392         boolean value = (boolean)mh.invokeExact(f, (Object)null);
393         assertTrue(value);
394     }
395 
396     // -- supporting methods --
397 
398     /**
399      * Returns a stream of all caller sensitive methods on public classes in packages
400      * exported by a named module.
401      */
402     static Stream<Method> callerSensitiveMethods(Module module) {
403         assert module.isNamed();
404         ModuleReference mref = module.getLayer().configuration()
405                 .findModule(module.getName())
406                 .orElseThrow(() -> new RuntimeException())
407                 .reference();
408         // find all ".class" resources in the module
409         // transform the resource name to a class name
410         // load every class in the exported packages
411         // return the caller sensitive methods of the public classes
412         try (ModuleReader reader = mref.open()) {
413             return reader.list()
414                     .filter(rn -> rn.endsWith(".class"))
415                     .map(rn -> rn.substring(0, rn.length() - 6)
416                                  .replace('/', '.'))
417                     .filter(cn -> module.isExported(packageName(cn)))
418                     .map(cn -> Class.forName(module, cn))
419                     .filter(refc -> refc != null
420                                     && Modifier.isPublic(refc.getModifiers()))
421                     .map(refc -> callerSensitiveMethods(refc))
422                     .flatMap(List::stream);
423         } catch (IOException ioe) {
424             throw new UncheckedIOException(ioe);
425         }
426     }
427 
428     static String packageName(String cn) {
429         int last = cn.lastIndexOf('.');
430         if (last > 0) {
431             return cn.substring(0, last);
432         } else {
433             return "";
434         }
435     }
436 
437     /**
438      * Returns a list of the caller sensitive methods directly declared by the given
439      * class.
440      */
441     static List<Method> callerSensitiveMethods(Class<?> refc) {
442         return Arrays.stream(refc.getDeclaredMethods())
443                 .filter(m -> m.isAnnotationPresent(CallerSensitive.class))
444                 .collect(Collectors.toList());
445     }
446 
447     /**
448      * Returns a short description of the given method for tracing purposes.
449      */
450     static String shortDescription(Method m) {
451         var sb = new StringBuilder();
452         sb.append(m.getDeclaringClass().getName());
453         sb.append('.');
454         sb.append(m.getName());
455         sb.append('(');
456         StringJoiner sj = new StringJoiner(",");
457         for (Class<?> parameterType : m.getParameterTypes()) {
458             sj.add(parameterType.getTypeName());
459         }
460         sb.append(sj);
461         sb.append(')');
462         return sb.toString();
463     }
464 }