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