/* * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.lang.classfile.ClassFile; import jdk.test.lib.util.ForceGC; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandleProxies; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.WeakReference; import java.util.Comparator; import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.MethodHandleProxies.*; import static java.lang.invoke.MethodType.methodType; import static java.lang.classfile.ClassFile.*; import static org.junit.jupiter.api.Assertions.*; /* * @test * @bug 6983726 * @library /test/lib * @enablePreview * @summary Tests on implementation hidden classes spinned by MethodHandleProxies * @build WrapperHiddenClassTest Client jdk.test.lib.util.ForceGC * @run junit WrapperHiddenClassTest */ public class WrapperHiddenClassTest { /** * Tests an adversary "implementation" class will not be * "recovered" by the wrapperInstance* APIs */ @Test public void testWrapperInstance() throws Throwable { Comparator hostile = createHostileInstance(); var mh = MethodHandles.publicLookup() .findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class)); @SuppressWarnings("unchecked") Comparator proxy = (Comparator) asInterfaceInstance(Comparator.class, mh); assertTrue(isWrapperInstance(proxy)); assertFalse(isWrapperInstance(hostile)); assertSame(mh, wrapperInstanceTarget(proxy)); assertThrows(IllegalArgumentException.class, () -> wrapperInstanceTarget(hostile)); assertSame(Comparator.class, wrapperInstanceType(proxy)); assertThrows(IllegalArgumentException.class, () -> wrapperInstanceType(hostile)); } private static final String TYPE = "interfaceType"; private static final String TARGET = "target"; private static final ClassDesc CD_HostileWrapper = ClassDesc.of("HostileWrapper"); private static final ClassDesc CD_Comparator = ClassDesc.of("java.util.Comparator"); private static final MethodTypeDesc MTD_int_Object_Object = MethodTypeDesc.of(CD_int, CD_Object, CD_Object); private static final MethodTypeDesc MTD_int_Integer = MethodTypeDesc.of(CD_int, CD_Integer); // Update this template when the MHP template is updated @SuppressWarnings("unchecked") private Comparator createHostileInstance() throws Throwable { var cf = ClassFile.of(); var bytes = cf.build(CD_HostileWrapper, clb -> { clb.withSuperclass(CD_Object); clb.withFlags(ACC_FINAL | ACC_SYNTHETIC); clb.withInterfaceSymbols(CD_Comparator); // static and instance fields clb.withField(TYPE, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); clb.withField(TARGET, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL); // clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> { cob.loadConstant(CD_Comparator); cob.putstatic(CD_HostileWrapper, TYPE, CD_Class); cob.return_(); }); // clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { cob.aload(0); cob.invokespecial(CD_Object, INIT_NAME, MTD_void); cob.return_(); }); // implementation clb.withMethodBody("compare", MTD_int_Object_Object, ACC_PUBLIC, cob -> { cob.aload(1); cob.checkcast(CD_Integer); cob.aload(2); cob.checkcast(CD_Integer); cob.invokestatic(CD_Integer, "compareTo", MTD_int_Integer); cob.ireturn(); }); }); var l = MethodHandles.lookup().defineHiddenClass(bytes, true); return (Comparator) l.findConstructor(l.lookupClass(), MethodType.methodType(void.class)).invoke(); } /** * Ensures a user interface cannot access a Proxy implementing it. */ @Test public void testNoAccess() { var instance = asInterfaceInstance(Client.class, MethodHandles.zero(void.class)); var instanceClass = instance.getClass(); var interfaceLookup = Client.lookup(); assertEquals(MethodHandles.Lookup.ORIGINAL, interfaceLookup.lookupModes() & MethodHandles.Lookup.ORIGINAL, "Missing original flag on interface's lookup"); assertThrows(IllegalAccessException.class, () -> MethodHandles.privateLookupIn(instanceClass, interfaceLookup)); } /** * Tests the Proxy module properties for Proxies implementing system and * user interfaces. */ @ParameterizedTest @ValueSource(classes = {Client.class, Runnable.class}) public void testModule(Class ifaceClass) { var mh = MethodHandles.zero(void.class); var inst = asInterfaceInstance(ifaceClass, mh); Module ifaceModule = ifaceClass.getModule(); Class implClass = inst.getClass(); Module implModule = implClass.getModule(); String implPackage = implClass.getPackageName(); assertFalse(implModule.isExported(implPackage), "implementation should not be exported"); assertTrue(ifaceModule.isExported(ifaceClass.getPackageName(), implModule), "interface package should be exported to implementation"); assertTrue(implModule.isOpen(implPackage, MethodHandleProxies.class.getModule()), "implementation class is not reflectively open to MHP class"); assertTrue(implModule.isNamed(), "dynamic module must be named"); assertTrue(implModule.getName().startsWith("jdk.MHProxy"), () -> "incorrect dynamic module name: " + implModule.getName()); assertSame(ifaceClass.getClassLoader(), implClass.getClassLoader(), "wrapper class should use the interface's loader "); assertSame(implClass.getClassLoader(), implModule.getClassLoader(), "module class loader should be wrapper class's loader"); } /** * Tests the access control of Proxies implementing system and user * interfaces. */ @ParameterizedTest @ValueSource(classes = {Client.class, Runnable.class}) public void testNoInstantiation(Class ifaceClass) throws ReflectiveOperationException { var mh = MethodHandles.zero(void.class); var instanceClass = asInterfaceInstance(ifaceClass, mh).getClass(); var ctor = instanceClass.getDeclaredConstructor(MethodHandles.Lookup.class, MethodHandle.class, MethodHandle.class); assertThrows(IllegalAccessException.class, () -> ctor.newInstance(Client.lookup(), mh, mh)); assertThrows(IllegalAccessException.class, () -> ctor.newInstance(MethodHandles.lookup(), mh, mh)); assertThrows(IllegalAccessException.class, () -> ctor.newInstance(MethodHandles.publicLookup(), mh, mh)); } /** * Tests the caching and weak reference of implementation classes for * system and user interfaces. */ @ParameterizedTest @ValueSource(classes = {Runnable.class, Client.class}) public void testWeakImplClass(Class ifaceClass) { var mh = MethodHandles.zero(void.class); var wrapper1 = asInterfaceInstance(ifaceClass, mh); var implClass = wrapper1.getClass(); System.gc(); // helps debug if incorrect items are weakly referenced var wrapper2 = asInterfaceInstance(ifaceClass, mh); assertSame(implClass, wrapper2.getClass(), "MHP should reuse old implementation class when available"); var implClassRef = new WeakReference<>(implClass); // clear strong references implClass = null; wrapper1 = null; wrapper2 = null; if (!ForceGC.wait(() -> implClassRef.refersTo(null))) { fail("MHP impl class cannot be cleared by GC"); } } }