f6d7e30b84
Reviewed-by: asotona
346 lines
13 KiB
Java
346 lines
13 KiB
Java
/*
|
|
* Copyright (c) 2017, 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.
|
|
*/
|
|
|
|
/* @test
|
|
* @modules java.base/java.lang:open
|
|
* @enablePreview
|
|
* @run testng/othervm test.DefineClassTest
|
|
* @summary Basic test for java.lang.invoke.MethodHandles.Lookup.defineClass
|
|
*/
|
|
|
|
package test;
|
|
|
|
import java.lang.classfile.ClassFile;
|
|
import java.lang.constant.ClassDesc;
|
|
import java.lang.invoke.MethodHandles.Lookup;
|
|
import java.lang.reflect.AccessFlag;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import org.testng.annotations.Test;
|
|
|
|
import static java.lang.classfile.ClassFile.ACC_PUBLIC;
|
|
import static java.lang.classfile.ClassFile.ACC_STATIC;
|
|
import static java.lang.constant.ConstantDescs.CD_Object;
|
|
import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME;
|
|
import static java.lang.constant.ConstantDescs.INIT_NAME;
|
|
import static java.lang.constant.ConstantDescs.MTD_void;
|
|
import static java.lang.invoke.MethodHandles.*;
|
|
import static java.lang.invoke.MethodHandles.Lookup.*;
|
|
import static org.testng.Assert.*;
|
|
|
|
public class DefineClassTest {
|
|
private static final String THIS_PACKAGE = DefineClassTest.class.getPackageName();
|
|
private static final ClassDesc CD_Runnable = Runnable.class.describeConstable().orElseThrow();
|
|
private static final ClassDesc CD_MissingSuperClass = ClassDesc.of("MissingSuperClass");
|
|
|
|
/**
|
|
* Test that a class has the same class loader, and is in the same package and
|
|
* protection domain, as a lookup class.
|
|
*/
|
|
void testSameAbode(Class<?> clazz, Class<?> lc) {
|
|
assertTrue(clazz.getClassLoader() == lc.getClassLoader());
|
|
assertEquals(clazz.getPackageName(), lc.getPackageName());
|
|
assertTrue(clazz.getProtectionDomain() == lc.getProtectionDomain());
|
|
}
|
|
|
|
/**
|
|
* Tests that a class is discoverable by name using Class.forName and
|
|
* lookup.findClass
|
|
*/
|
|
void testDiscoverable(Class<?> clazz, Lookup lookup) throws Exception {
|
|
String cn = clazz.getName();
|
|
ClassLoader loader = clazz.getClassLoader();
|
|
assertTrue(Class.forName(cn, false, loader) == clazz);
|
|
assertTrue(lookup.findClass(cn) == clazz);
|
|
}
|
|
|
|
/**
|
|
* Basic test of defineClass to define a class in the same package as test.
|
|
*/
|
|
@Test
|
|
public void testDefineClass() throws Exception {
|
|
final String CLASS_NAME = THIS_PACKAGE + ".Foo";
|
|
Lookup lookup = lookup();
|
|
Class<?> clazz = lookup.defineClass(generateClass(CLASS_NAME));
|
|
|
|
// test name
|
|
assertEquals(clazz.getName(), CLASS_NAME);
|
|
|
|
// test loader/package/protection-domain
|
|
testSameAbode(clazz, lookup.lookupClass());
|
|
|
|
// test discoverable
|
|
testDiscoverable(clazz, lookup);
|
|
|
|
// attempt defineClass again
|
|
try {
|
|
lookup.defineClass(generateClass(CLASS_NAME));
|
|
assertTrue(false);
|
|
} catch (LinkageError expected) { }
|
|
}
|
|
|
|
/**
|
|
* Test public/package/protected/private access from class defined with defineClass.
|
|
*/
|
|
@Test
|
|
public void testAccess() throws Exception {
|
|
final String THIS_CLASS = this.getClass().getName();
|
|
final String CLASS_NAME = THIS_PACKAGE + ".Runner";
|
|
Lookup lookup = lookup();
|
|
|
|
// public
|
|
byte[] classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method1");
|
|
testInvoke(lookup.defineClass(classBytes));
|
|
|
|
// package
|
|
classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method2");
|
|
testInvoke(lookup.defineClass(classBytes));
|
|
|
|
// protected (same package)
|
|
classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method3");
|
|
testInvoke(lookup.defineClass(classBytes));
|
|
|
|
// private
|
|
classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method4");
|
|
Class<?> clazz = lookup.defineClass(classBytes);
|
|
Runnable r = (Runnable) clazz.newInstance();
|
|
try {
|
|
r.run();
|
|
assertTrue(false);
|
|
} catch (IllegalAccessError expected) { }
|
|
}
|
|
|
|
public static void method1() { }
|
|
static void method2() { }
|
|
protected static void method3() { }
|
|
private static void method4() { }
|
|
|
|
void testInvoke(Class<?> clazz) throws Exception {
|
|
Object obj = clazz.newInstance();
|
|
((Runnable) obj).run();
|
|
}
|
|
|
|
/**
|
|
* Test that defineClass does not run the class initializer
|
|
*/
|
|
@Test
|
|
public void testInitializerNotRun() throws Exception {
|
|
final String THIS_CLASS = this.getClass().getName();
|
|
final String CLASS_NAME = THIS_PACKAGE + ".ClassWithClinit";
|
|
|
|
byte[] classBytes = generateClassWithInitializer(CLASS_NAME, THIS_CLASS, "fail");
|
|
Class<?> clazz = lookup().defineClass(classBytes);
|
|
|
|
// trigger initializer to run
|
|
try {
|
|
clazz.newInstance();
|
|
assertTrue(false);
|
|
} catch (ExceptionInInitializerError e) {
|
|
assertTrue(e.getCause() instanceof IllegalCallerException);
|
|
}
|
|
}
|
|
|
|
static void fail() { throw new IllegalCallerException(); }
|
|
|
|
|
|
/**
|
|
* Test defineClass to define classes in a package containing classes with
|
|
* different protection domains.
|
|
*/
|
|
@Test
|
|
public void testTwoProtectionDomains() throws Exception {
|
|
Path here = Paths.get("");
|
|
|
|
// p.C1 in one exploded directory
|
|
Path dir1 = Files.createTempDirectory(here, "classes");
|
|
Path p = Files.createDirectory(dir1.resolve("p"));
|
|
Files.write(p.resolve("C1.class"), generateClass("p.C1"));
|
|
URL url1 = dir1.toUri().toURL();
|
|
|
|
// p.C2 in another exploded directory
|
|
Path dir2 = Files.createTempDirectory(here, "classes");
|
|
p = Files.createDirectory(dir2.resolve("p"));
|
|
Files.write(p.resolve("C2.class"), generateClass("p.C2"));
|
|
URL url2 = dir2.toUri().toURL();
|
|
|
|
// load p.C1 and p.C2
|
|
ClassLoader loader = new URLClassLoader(new URL[] { url1, url2 });
|
|
Class<?> target1 = Class.forName("p.C1", false, loader);
|
|
Class<?> target2 = Class.forName("p.C2", false, loader);
|
|
assertTrue(target1.getClassLoader() == loader);
|
|
assertTrue(target1.getClassLoader() == loader);
|
|
assertNotEquals(target1.getProtectionDomain(), target2.getProtectionDomain());
|
|
|
|
// protection domain 1
|
|
Lookup lookup1 = privateLookupIn(target1, lookup());
|
|
|
|
Class<?> clazz = lookup1.defineClass(generateClass("p.Foo"));
|
|
testSameAbode(clazz, lookup1.lookupClass());
|
|
testDiscoverable(clazz, lookup1);
|
|
|
|
// protection domain 2
|
|
Lookup lookup2 = privateLookupIn(target2, lookup());
|
|
|
|
clazz = lookup2.defineClass(generateClass("p.Bar"));
|
|
testSameAbode(clazz, lookup2.lookupClass());
|
|
testDiscoverable(clazz, lookup2);
|
|
}
|
|
|
|
/**
|
|
* Test defineClass defining a class to the boot loader
|
|
*/
|
|
@Test
|
|
public void testBootLoader() throws Exception {
|
|
Lookup lookup = privateLookupIn(Thread.class, lookup());
|
|
assertTrue(lookup.getClass().getClassLoader() == null);
|
|
|
|
Class<?> clazz = lookup.defineClass(generateClass("java.lang.Foo"));
|
|
assertEquals(clazz.getName(), "java.lang.Foo");
|
|
testSameAbode(clazz, Thread.class);
|
|
testDiscoverable(clazz, lookup);
|
|
}
|
|
|
|
@Test(expectedExceptions = { IllegalArgumentException.class })
|
|
public void testWrongPackage() throws Exception {
|
|
lookup().defineClass(generateClass("other.C"));
|
|
}
|
|
|
|
@Test(expectedExceptions = { IllegalAccessException.class })
|
|
public void testNoPackageAccess() throws Exception {
|
|
Lookup lookup = lookup().dropLookupMode(PACKAGE);
|
|
lookup.defineClass(generateClass(THIS_PACKAGE + ".C"));
|
|
}
|
|
|
|
@Test(expectedExceptions = { ClassFormatError.class })
|
|
public void testTruncatedClassFile() throws Exception {
|
|
lookup().defineClass(new byte[0]);
|
|
}
|
|
|
|
@Test(expectedExceptions = { NullPointerException.class })
|
|
public void testNull() throws Exception {
|
|
lookup().defineClass(null);
|
|
}
|
|
|
|
@Test(expectedExceptions = { NoClassDefFoundError.class })
|
|
public void testLinking() throws Exception {
|
|
lookup().defineClass(generateNonLinkableClass(THIS_PACKAGE + ".NonLinkableClass"));
|
|
}
|
|
|
|
@Test(expectedExceptions = { IllegalArgumentException.class })
|
|
public void testModuleInfo() throws Exception {
|
|
lookup().defineClass(generateModuleInfo());
|
|
}
|
|
|
|
/**
|
|
* Generates a class file with the given class name
|
|
*/
|
|
byte[] generateClass(String className) {
|
|
return ClassFile.of().build(ClassDesc.of(className), clb -> {
|
|
clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
|
|
clb.withSuperclass(CD_Object);
|
|
clb.withMethodBody(INIT_NAME, MTD_void, PUBLIC, cob -> {
|
|
cob.aload(0);
|
|
cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
|
|
cob.return_();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generate a class file with the given class name. The class implements Runnable
|
|
* with a run method to invokestatic the given targetClass/targetMethod.
|
|
*/
|
|
byte[] generateRunner(String className,
|
|
String targetClass,
|
|
String targetMethod) throws Exception {
|
|
|
|
return ClassFile.of().build(ClassDesc.of(className), clb -> {
|
|
clb.withSuperclass(CD_Object);
|
|
clb.withInterfaceSymbols(CD_Runnable);
|
|
clb.withMethodBody(INIT_NAME, MTD_void, PUBLIC, cob -> {
|
|
cob.aload(0);
|
|
cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
|
|
cob.return_();
|
|
});
|
|
clb.withMethodBody("run", MTD_void, PUBLIC, cob -> {
|
|
cob.invokestatic(ClassDesc.of(targetClass), targetMethod, MTD_void);
|
|
cob.return_();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generate a class file with the given class name. The class will initializer
|
|
* to invokestatic the given targetClass/targetMethod.
|
|
*/
|
|
byte[] generateClassWithInitializer(String className,
|
|
String targetClass,
|
|
String targetMethod) throws Exception {
|
|
|
|
return ClassFile.of().build(ClassDesc.of(className), clb -> {
|
|
clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
|
|
clb.withSuperclass(CD_Object);
|
|
clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> {
|
|
cob.aload(0);
|
|
cob.invokespecial(CD_Object, INIT_NAME, MTD_void);
|
|
cob.return_();
|
|
});
|
|
clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> {
|
|
cob.invokestatic(ClassDesc.of(targetClass), targetMethod, MTD_void);
|
|
cob.return_();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generates a non-linkable class file with the given class name
|
|
*/
|
|
byte[] generateNonLinkableClass(String className) {
|
|
return ClassFile.of().build(ClassDesc.of(className), clb -> {
|
|
clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER);
|
|
clb.withSuperclass(CD_MissingSuperClass);
|
|
clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> {
|
|
cob.aload(0);
|
|
cob.invokespecial(CD_MissingSuperClass, INIT_NAME, MTD_void);
|
|
cob.return_();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generates a class file with the given class name
|
|
*/
|
|
byte[] generateModuleInfo() {
|
|
return ClassFile.of().build(ClassDesc.of("module-info"), cb -> cb.withFlags(AccessFlag.MODULE));
|
|
}
|
|
|
|
private int nextNumber() {
|
|
return ++nextNumber;
|
|
}
|
|
|
|
private int nextNumber;
|
|
}
|