--- /dev/null 2017-03-09 18:14:19.024999985 +0000 +++ new/test/java/lang/invoke/DefineClassTest.java 2017-03-21 13:44:40.709007316 +0000 @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2017, 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 + * java.base/jdk.internal.org.objectweb.asm + * @run testng/othervm test.DefineClassTest + * @summary Basic test for java.lang.invoke.MethodHandles.Lookup.defineClass + */ + +package test; + +import java.lang.invoke.MethodHandles.Lookup; +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodHandles.Lookup.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class DefineClassTest { + private static final String THIS_PACKAGE = DefineClassTest.class.getPackageName(); + + /** + * 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().dropLookupMode(PRIVATE); + 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().dropLookupMode(PRIVATE); + + // 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"); + Lookup lookup = lookup().dropLookupMode(PRIVATE); + + 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 { + // p.C1 in one exploded directory + Path dir1 = Files.createTempDirectory("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("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()).dropLookupMode(PRIVATE); + + Class clazz = lookup1.defineClass(generateClass("p.Foo")); + testSameAbode(clazz, lookup1.lookupClass()); + testDiscoverable(clazz, lookup1); + + // protection domain 2 + Lookup lookup2 = privateLookupIn(target2, lookup()).dropLookupMode(PRIVATE); + + 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()).dropLookupMode(PRIVATE); + 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 lookup = lookup().dropLookupMode(PRIVATE); + 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 = { UnsupportedOperationException.class }) + public void testHasPrivateAccess() throws Exception { + Lookup lookup = lookup(); + assertTrue(lookup.hasPrivateAccess()); + lookup.defineClass(generateClass(THIS_PACKAGE + ".C")); + } + + @Test(expectedExceptions = { ClassFormatError.class }) + public void testTruncatedClassFile() throws Exception { + Lookup lookup = lookup().dropLookupMode(PRIVATE); + lookup.defineClass(new byte[0]); + } + + @Test(expectedExceptions = { NullPointerException.class }) + public void testNull() throws Exception { + Lookup lookup = lookup().dropLookupMode(PRIVATE); + lookup.defineClass(null); + } + + /** + * Generates a class file with the given class name + */ + byte[] generateClass(String className) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + + ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_9, + ACC_PUBLIC + ACC_SUPER, + className.replace(".", "/"), + null, + "java/lang/Object", + null); + + // + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + /** + * 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 { + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + + ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_9, + ACC_PUBLIC + ACC_SUPER, + className.replace(".", "/"), + null, + "java/lang/Object", + new String[] { "java/lang/Runnable" }); + + // + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // run() + String tc = targetClass.replace(".", "/"); + mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null); + mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + /** + * 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 { + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + + ClassWriter.COMPUTE_FRAMES); + cw.visit(V1_9, + ACC_PUBLIC + ACC_SUPER, + className.replace(".", "/"), + null, + "java/lang/Object", + null); + + // + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + // + String tc = targetClass.replace(".", "/"); + mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); + mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + private int nextNumber() { + return ++nextNumber; + } + + private int nextNumber; +}