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 }