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 }