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