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