1 /*
2 * Copyright (c) 2023, 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 import java.lang.classfile.ClassFile;
25 import jdk.test.lib.util.ForceGC;
26 import org.junit.jupiter.api.Test;
27 import org.junit.jupiter.params.ParameterizedTest;
28 import org.junit.jupiter.params.provider.ValueSource;
29
30 import java.lang.constant.ClassDesc;
31 import java.lang.constant.MethodTypeDesc;
32 import java.lang.invoke.MethodHandle;
33 import java.lang.invoke.MethodHandleProxies;
34 import java.lang.invoke.MethodHandles;
35 import java.lang.invoke.MethodType;
36 import java.lang.ref.WeakReference;
37 import java.util.Comparator;
38
39 import static java.lang.constant.ConstantDescs.*;
40 import static java.lang.invoke.MethodHandleProxies.*;
41 import static java.lang.invoke.MethodType.methodType;
42 import static java.lang.classfile.ClassFile.*;
43 import static org.junit.jupiter.api.Assertions.*;
44
45 /*
46 * @test
47 * @bug 6983726
48 * @library /test/lib
49 * @summary Tests on implementation hidden classes spinned by MethodHandleProxies
50 * @build WrapperHiddenClassTest Client jdk.test.lib.util.ForceGC
51 * @run junit WrapperHiddenClassTest
52 */
53 public class WrapperHiddenClassTest {
54
55 /**
56 * Tests an adversary "implementation" class will not be
57 * "recovered" by the wrapperInstance* APIs
58 */
59 @Test
60 public void testWrapperInstance() throws Throwable {
61 Comparator<Integer> hostile = createHostileInstance();
62 var mh = MethodHandles.publicLookup()
63 .findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class));
64 @SuppressWarnings("unchecked")
65 Comparator<Integer> proxy = (Comparator<Integer>) asInterfaceInstance(Comparator.class, mh);
66
67 assertTrue(isWrapperInstance(proxy));
68 assertFalse(isWrapperInstance(hostile));
69 assertSame(mh, wrapperInstanceTarget(proxy));
70 assertThrows(IllegalArgumentException.class, () -> wrapperInstanceTarget(hostile));
71 assertSame(Comparator.class, wrapperInstanceType(proxy));
72 assertThrows(IllegalArgumentException.class, () -> wrapperInstanceType(hostile));
73 }
74
75 private static final String TYPE = "interfaceType";
76 private static final String TARGET = "target";
77 private static final ClassDesc CD_HostileWrapper = ClassDesc.of("HostileWrapper");
78 private static final ClassDesc CD_Comparator = ClassDesc.of("java.util.Comparator");
79 private static final MethodTypeDesc MTD_int_Object_Object = MethodTypeDesc.of(CD_int, CD_Object, CD_Object);
80 private static final MethodTypeDesc MTD_int_Integer = MethodTypeDesc.of(CD_int, CD_Integer);
81
82 // Update this template when the MHP template is updated
83 @SuppressWarnings("unchecked")
84 private Comparator<Integer> createHostileInstance() throws Throwable {
85 var cf = ClassFile.of();
86 var bytes = cf.build(CD_HostileWrapper, clb -> {
87 clb.withSuperclass(CD_Object);
88 clb.withFlags(ACC_FINAL | ACC_SYNTHETIC);
89 clb.withInterfaceSymbols(CD_Comparator);
90
91 // static and instance fields
92 clb.withField(TYPE, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL);
93 clb.withField(TARGET, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL);
94
95 // <clinit>
96 clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
97 cob.loadConstant(CD_Comparator);
98 cob.putstatic(CD_HostileWrapper, TYPE, CD_Class);
99 cob.return_();
100 });
101
102 // <init>
103 clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> {
104 cob.aload(0);
105 cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
106 cob.return_();
107 });
108
109 // implementation
110 clb.withMethodBody("compare", MTD_int_Object_Object, ACC_PUBLIC, cob -> {
111 cob.aload(1);
112 cob.checkcast(CD_Integer);
113 cob.aload(2);
114 cob.checkcast(CD_Integer);
115 cob.invokestatic(CD_Integer, "compareTo", MTD_int_Integer);
116 cob.ireturn();
117 });
118 });
119 var l = MethodHandles.lookup().defineHiddenClass(bytes, true);
120 return (Comparator<Integer>) l.findConstructor(l.lookupClass(), MethodType.methodType(void.class)).invoke();
121 }
122
123 /**
124 * Ensures a user interface cannot access a Proxy implementing it.
125 */
126 @Test
127 public void testNoAccess() {
128 var instance = asInterfaceInstance(Client.class, MethodHandles.zero(void.class));
129 var instanceClass = instance.getClass();
130 var interfaceLookup = Client.lookup();
131 assertEquals(MethodHandles.Lookup.ORIGINAL, interfaceLookup.lookupModes() & MethodHandles.Lookup.ORIGINAL,
132 "Missing original flag on interface's lookup");
133 assertThrows(IllegalAccessException.class, () -> MethodHandles.privateLookupIn(instanceClass,
134 interfaceLookup));
135 }
136
137 /**
138 * Tests the Proxy module properties for Proxies implementing system and
139 * user interfaces.
140 */
141 @ParameterizedTest
142 @ValueSource(classes = {Client.class, Runnable.class})
143 public void testModule(Class<?> ifaceClass) {
144 var mh = MethodHandles.zero(void.class);
145
146 var inst = asInterfaceInstance(ifaceClass, mh);
147 Module ifaceModule = ifaceClass.getModule();
148 Class<?> implClass = inst.getClass();
149 Module implModule = implClass.getModule();
150
151 String implPackage = implClass.getPackageName();
152 assertFalse(implModule.isExported(implPackage),
153 "implementation should not be exported");
154 assertTrue(ifaceModule.isExported(ifaceClass.getPackageName(), implModule),
155 "interface package should be exported to implementation");
156 assertTrue(implModule.isOpen(implPackage, MethodHandleProxies.class.getModule()),
157 "implementation class is not reflectively open to MHP class");
158 assertTrue(implModule.isNamed(), "dynamic module must be named");
159 assertTrue(implModule.getName().startsWith("jdk.MHProxy"),
160 () -> "incorrect dynamic module name: " + implModule.getName());
161
162 assertSame(ifaceClass.getClassLoader(), implClass.getClassLoader(),
163 "wrapper class should use the interface's loader ");
164 assertSame(implClass.getClassLoader(), implModule.getClassLoader(),
165 "module class loader should be wrapper class's loader");
166 }
167
168 /**
169 * Tests the access control of Proxies implementing system and user
170 * interfaces.
171 */
172 @ParameterizedTest
173 @ValueSource(classes = {Client.class, Runnable.class})
174 public void testNoInstantiation(Class<?> ifaceClass) throws ReflectiveOperationException {
175 var mh = MethodHandles.zero(void.class);
176 var instanceClass = asInterfaceInstance(ifaceClass, mh).getClass();
177 var ctor = instanceClass.getDeclaredConstructor(MethodHandles.Lookup.class, MethodHandle.class, MethodHandle.class);
178
179 assertThrows(IllegalAccessException.class, () -> ctor.newInstance(Client.lookup(), mh, mh));
180 assertThrows(IllegalAccessException.class, () -> ctor.newInstance(MethodHandles.lookup(), mh, mh));
181 assertThrows(IllegalAccessException.class, () -> ctor.newInstance(MethodHandles.publicLookup(), mh, mh));
182 }
183
184 /**
185 * Tests the caching and weak reference of implementation classes for
186 * system and user interfaces.
187 */
188 @ParameterizedTest
189 @ValueSource(classes = {Runnable.class, Client.class})
190 public void testWeakImplClass(Class<?> ifaceClass) {
191 var mh = MethodHandles.zero(void.class);
192
193 var wrapper1 = asInterfaceInstance(ifaceClass, mh);
194 var implClass = wrapper1.getClass();
195
196 System.gc(); // helps debug if incorrect items are weakly referenced
197 var wrapper2 = asInterfaceInstance(ifaceClass, mh);
198 assertSame(implClass, wrapper2.getClass(),
199 "MHP should reuse old implementation class when available");
200
201 var implClassRef = new WeakReference<>(implClass);
202 // clear strong references
203 implClass = null;
204 wrapper1 = null;
205 wrapper2 = null;
206
207 if (!ForceGC.wait(() -> implClassRef.refersTo(null))) {
208 fail("MHP impl class cannot be cleared by GC");
209 }
210 }
211 }
--- EOF ---