ae82405ff7
Reviewed-by: briangoetz, psandoz
213 lines
9.1 KiB
Java
213 lines
9.1 KiB
Java
/*
|
|
* 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<Integer> hostile = createHostileInstance();
|
|
var mh = MethodHandles.publicLookup()
|
|
.findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class));
|
|
@SuppressWarnings("unchecked")
|
|
Comparator<Integer> proxy = (Comparator<Integer>) 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<Integer> 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);
|
|
|
|
// <clinit>
|
|
clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
|
|
cob.loadConstant(CD_Comparator);
|
|
cob.putstatic(CD_HostileWrapper, TYPE, CD_Class);
|
|
cob.return_();
|
|
});
|
|
|
|
// <init>
|
|
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<Integer>) 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");
|
|
}
|
|
}
|
|
}
|