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