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 8327639
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.BeforeAll;
55 import org.junit.jupiter.api.Test;
56 import org.junit.jupiter.params.ParameterizedTest;
57 import org.junit.jupiter.params.provider.MethodSource;
58
59 public class CallerSensitiveAccess {
60 // Cache the list of Caller Sensitive Methods
61 private static List<Method> CALLER_SENSITIVE_METHODS;
62
63 @BeforeAll
64 public static 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 static Object[][] callerSensitiveMethods() {
72 return CALLER_SENSITIVE_METHODS.stream()
73 .map(m -> new Object[]{m, shortDescription(m)})
74 .toArray(Object[][]::new);
75 }
76
77 /**
78 * Using publicLookup, attempt to use findVirtual or findStatic to obtain a
79 * method handle to a caller sensitive method.
80 */
81 @ParameterizedTest
82 @MethodSource("callerSensitiveMethods")
83 public void testPublicLookupFind(Method method, String desc) throws Exception {
84 Lookup lookup = MethodHandles.publicLookup();
85 Class<?> refc = method.getDeclaringClass();
86 String name = method.getName();
87 MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
88 assertThrows(IllegalAccessException.class, () -> {
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 /**
98 * Using publicLookup, attempt to use unreflect to obtain a method handle to a
99 * caller sensitive method.
100 */
101 @ParameterizedTest
102 @MethodSource("callerSensitiveMethods")
103 public void testPublicLookupUnreflect(Method method, String desc) throws Exception {
104 assertThrows(IllegalAccessException.class, () -> MethodHandles.publicLookup().unreflect(method));
105 }
106
107 /**
108 * public accessible caller sensitive methods in APIs exported by java.base.
109 */
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 @ParameterizedTest
123 @MethodSource("accessibleCallerSensitiveMethods")
124 public void testLookupUnreflect(Method method, String desc) throws Exception {
125 assertThrows(IllegalAccessException.class, () -> 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 @ParameterizedTest
133 @MethodSource("callerSensitiveMethods")
134 public void testLookupNoOriginalAccessFind(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 assertThrows(IllegalAccessException.class, () -> {
141 if (Modifier.isStatic(method.getModifiers())) {
142 lookup.findStatic(refc, name, mt);
143 } else {
144 lookup.findVirtual(refc, name, mt);
145 }
146 });
147 }
148
149 /**
150 * Using a Lookup with no original access that can't unreflect caller-sensitive
151 * method
152 */
153 @ParameterizedTest
154 @MethodSource("callerSensitiveMethods")
155 public void testLookupNoOriginalAccessUnreflect(Method method, String desc) throws Exception {
156 Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL);
157 assertTrue(lookup.hasFullPrivilegeAccess());
158 assertThrows(IllegalAccessException.class, () -> lookup.unreflect(method));
159 }
160
161 // -- Test method handles to setAccessible --
162
163 private int aField;
164
165 Field accessibleField() {
166 var clazz = getClass();
167 return assertDoesNotThrow(() -> clazz.getDeclaredField("aField"));
168 }
169
170 Field inaccessibleField() {
171 return assertDoesNotThrow(() -> String.class.getDeclaredField("hash"));
172 }
173
174 void findAndInvokeSetAccessible(Class<? extends AccessibleObject> refc, Field f) throws Throwable {
175 MethodType mt = MethodType.methodType(void.class, boolean.class);
176 MethodHandle mh = MethodHandles.lookup().findVirtual(refc, "setAccessible", mt);
177 mh.invoke(f, true); // may throw InaccessibleObjectException
178 assertTrue(f.isAccessible());
179 }
180
181 void unreflectAndInvokeSetAccessible(Method m, Field f) throws Throwable {
182 assertTrue(m.getName().equals("setAccessible"));
183 assertFalse(Modifier.isStatic(m.getModifiers()));
184 MethodHandle mh = MethodHandles.lookup().unreflect(m);
185 mh.invoke(f, true); // may throw InaccessibleObjectException
186 assertTrue(f.isAccessible());
187 }
188
189 /**
190 * Create a method handle to setAccessible and use it to suppress access to an
191 * accessible member.
192 */
193 @Test
194 public void testSetAccessible1() throws Throwable {
195 findAndInvokeSetAccessible(AccessibleObject.class, accessibleField());
196 }
197 @Test
198 public void testSetAccessible2() throws Throwable {
199 findAndInvokeSetAccessible(Field.class, accessibleField());
200 }
201 @Test
202 public void testSetAccessible3() throws Throwable {
203 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class);
204 unreflectAndInvokeSetAccessible(m, accessibleField());
205 }
206 @Test
207 public void testSetAccessible4() throws Throwable {
208 Method m = Field.class.getMethod("setAccessible", boolean.class);
209 unreflectAndInvokeSetAccessible(m, accessibleField());
210 }
211
212 /**
213 * Create a method handle to setAccessible and attempt to use it to suppress
214 * access to an inaccessible member.
215 */
216 @Test
217 public void testSetAccessible5() throws Throwable {
218 assertThrows(InaccessibleObjectException.class, () -> findAndInvokeSetAccessible(AccessibleObject.class, inaccessibleField()));
219 }
220 @Test
221 public void testSetAccessible6() throws Throwable {
222 assertThrows(InaccessibleObjectException.class, () -> findAndInvokeSetAccessible(Field.class, inaccessibleField()));
223 }
224 @Test
225 public void testSetAccessible7() throws Throwable {
226 Method m = AccessibleObject.class.getMethod("setAccessible", boolean.class);
227 assertThrows(InaccessibleObjectException.class, () -> unreflectAndInvokeSetAccessible(m, inaccessibleField()));
228 }
229 @Test
230 public void testSetAccessible8() throws Throwable {
231 Method m = Field.class.getMethod("setAccessible", boolean.class);
232 assertThrows(InaccessibleObjectException.class, () -> unreflectAndInvokeSetAccessible(m, inaccessibleField()));
233 }
234
235
236 // -- Test sub-classes of AccessibleObject --
237
238 /**
239 * Custom AccessibleObject objects. One class overrides setAccessible, the other
240 * does not override this method.
241 */
242 static Object[][] customAccessibleObjectClasses() {
243 return new Object[][] { { new S1() }, { new S2() } };
244 }
245 public static class S1 extends AccessibleObject { }
246 public static class S2 extends AccessibleObject {
247 @Override
248 public void setAccessible(boolean flag) {
249 super.setAccessible(flag);
250 }
251 }
252
253 void findAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable {
254 MethodType mt = MethodType.methodType(void.class, boolean.class);
255 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt);
256 mh.invoke(obj, true);
257 assertTrue(obj.isAccessible());
258 }
259
260 void unreflectAndInvokeSetAccessible(Lookup lookup, AccessibleObject obj) throws Throwable {
261 Method m = obj.getClass().getMethod("setAccessible", boolean.class);
262 MethodHandle mh = lookup.unreflect(m);
263 mh.invoke(obj, true);
264 assertTrue(obj.isAccessible());
265 }
266
267 /**
268 * Using publicLookup, create a method handle to setAccessible and invoke it
269 * on a custom AccessibleObject object.
270 */
271 @Test
272 public void testPublicLookupSubclass1() throws Throwable {
273 // S1 does not override setAccessible
274 assertThrows(IllegalAccessException.class, () -> findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1()));
275 }
276 @Test
277 public void testPublicLookupSubclass2() throws Throwable {
278 // S2 overrides setAccessible
279 findAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2());
280 }
281 @Test
282 public void testPublicLookupSubclass3() throws Throwable {
283 // S1 does not override setAccessible
284 assertThrows(IllegalAccessException.class, () -> unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S1()));
285 }
286 @Test
287 public void testPublicLookupSubclass4() throws Throwable {
288 // S2 overrides setAccessible
289 unreflectAndInvokeSetAccessible(MethodHandles.publicLookup(), new S2());
290 }
291
292 /**
293 * Using a full power lookup, create a method handle to setAccessible and
294 * invoke it on a custom AccessibleObject object.
295 */
296 @ParameterizedTest
297 @MethodSource("customAccessibleObjectClasses")
298 public void testLookupSubclass1(AccessibleObject obj) throws Throwable {
299 findAndInvokeSetAccessible(MethodHandles.lookup(), obj);
300 }
301 @ParameterizedTest
302 @MethodSource("customAccessibleObjectClasses")
303 public void testLookupSubclass2(AccessibleObject obj) throws Throwable {
304 unreflectAndInvokeSetAccessible(MethodHandles.lookup(), obj);
305 }
306
307 /**
308 * Using a full power lookup, create a method handle to setAccessible on a
309 * sub-class of AccessibleObject and then attempt to invoke it on a Field object.
310 */
311 @ParameterizedTest
312 @MethodSource("customAccessibleObjectClasses")
313 public void testLookupSubclass3(AccessibleObject obj) throws Throwable {
314 MethodType mt = MethodType.methodType(void.class, boolean.class);
315 Lookup lookup = MethodHandles.lookup();
316 MethodHandle mh = lookup.findVirtual(obj.getClass(), "setAccessible", mt);
317 assertThrows(ClassCastException.class, () -> mh.invoke(accessibleField(), true));
318 }
319
320 /**
321 * Using a full power lookup, use unreflect to create a method handle to
322 * setAccessible on a sub-class of AccessibleObject, then attempt to invoke
323 * it on a Field object.
324 */
325 @Test
326 public void testLookupSubclass4() throws Throwable {
327 // S1 does not override setAccessible
328 Method m = S1.class.getMethod("setAccessible", boolean.class);
329 assertTrue(m.getDeclaringClass() == AccessibleObject.class);
330 MethodHandle mh = MethodHandles.lookup().unreflect(m);
331 Field f = accessibleField();
332 mh.invoke(f, true);
333 assertTrue(f.isAccessible());
334 }
335 @Test
336 public void testLookupSubclass5() throws Throwable {
337 // S1 does not override setAccessible
338 Method m = S1.class.getMethod("setAccessible", boolean.class);
339 assertTrue(m.getDeclaringClass() == AccessibleObject.class);
340 MethodHandle mh = MethodHandles.lookup().unreflect(m);
341 assertThrows(InaccessibleObjectException.class, () -> mh.invoke(inaccessibleField(), true));
342 }
343 @Test
344 public void testLookupSubclass6() throws Throwable {
345 // S2 overrides setAccessible
346 Method m = S2.class.getMethod("setAccessible", boolean.class);
347 assertTrue(m.getDeclaringClass() == S2.class);
348 MethodHandle mh = MethodHandles.lookup().unreflect(m);
349 assertThrows(ClassCastException.class, () -> mh.invoke(accessibleField(), true));
350 }
351 @Test
352 public void testLookupSubclass7() throws Throwable {
353 // S2 overrides setAccessible
354 Method m = S2.class.getMethod("setAccessible", boolean.class);
355 assertTrue(m.getDeclaringClass() == S2.class);
356 MethodHandle mh = MethodHandles.lookup().unreflect(m);
357 assertThrows(ClassCastException.class, () -> mh.invoke(inaccessibleField(), true));
358 }
359
360 /**
361 * Field::getInt and Field::setInt are caller-sensitive methods.
362 * Test access to private field.
363 */
364 private static int privateField = 0;
365 @Test
366 public void testPrivateField() throws Throwable {
367 Field f = CallerSensitiveAccess.class.getDeclaredField("privateField");
368 // Field::setInt
369 MethodType mtype = MethodType.methodType(void.class, Object.class, int.class);
370 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setInt", mtype);
371 mh.invokeExact(f, (Object)null, 5);
372 // Field::getInt
373 mh = MethodHandles.lookup().findVirtual(Field.class, "getInt", MethodType.methodType(int.class, Object.class));
374 int value = (int)mh.invokeExact(f, (Object)null);
375 assertEquals(5, value);
376 }
377
378 private static class Inner {
379 private static boolean privateField = false;
380 }
381
382 @Test
383 public void testInnerPrivateField() throws Throwable {
384 Field f = Inner.class.getDeclaredField("privateField");
385 // Field::setInt
386 MethodType mtype = MethodType.methodType(void.class, Object.class, boolean.class);
387 MethodHandle mh = MethodHandles.lookup().findVirtual(Field.class, "setBoolean", mtype);
388 mh.invokeExact(f, (Object)null, true);
389 // Field::getInt
390 mh = MethodHandles.lookup().findVirtual(Field.class, "getBoolean", MethodType.methodType(boolean.class, Object.class));
391 boolean value = (boolean)mh.invokeExact(f, (Object)null);
392 assertTrue(value);
393 }
394
395 // -- supporting methods --
396
397 /**
398 * Returns a List of all caller sensitive methods on public classes in packages
399 * exported by a named module.
400 * Returns a List instead of a stream so the ModuleReader can be closed before returning.
401 */
402 static List<Method> callerSensitiveMethods(Module module) {
403 assertTrue(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).toList();
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 }