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 }