/* * Copyright (c) 2018, 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 * @library /test/lib * @modules java.base/jdk.internal.org.objectweb.asm * @build DefineClassWithClassData * @run testng/othervm DefineClassWithClassData */ import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.stream.Stream; import jdk.internal.org.objectweb.asm.*; import org.testng.annotations.Test; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; import static java.lang.invoke.MethodHandles.Lookup.PRIVATE; import static jdk.internal.org.objectweb.asm.Opcodes.*; import static org.testng.Assert.*; public class DefineClassWithClassData { private static final byte[] T_CLASS_BYTES = ClassByteBuilder.classBytes("T"); private static final byte[] T2_CLASS_BYTES = ClassByteBuilder.classBytes("T2"); private int privMethod() { return 1234; } /* * invoke int test(DefineClassWithClassData o) method defined in the injected class */ private int testInjectedClass(Class c) throws Throwable { try { Method m = c.getMethod("test", DefineClassWithClassData.class); return (int) m.invoke(c.newInstance(), this); } catch (InvocationTargetException e) { throw e.getCause(); } } /* * Returns the value of the static final "data" field in the injected class */ private Object injectedData(Class c) throws Throwable { return c.getDeclaredField("data").get(null); } private static final List classData = List.of("nestmate", "classdata"); @Test public void defineNestMate() throws Throwable { // define a nestmate Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, true, NESTMATE); Class c = lookup.lookupClass(); assertTrue(c.getNestHost() == DefineClassWithClassData.class); assertEquals(classData, injectedData(c)); // invoke int test(DefineClassWithClassData o) int x = testInjectedClass(c); assertTrue(x == privMethod()); // dynamic nestmate is not listed in the return array of getNestMembers assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c)); assertTrue(c.isNestmateOf(DefineClassWithClassData.class)); } @Test public void defineHiddenClass() throws Throwable { // define a hidden class Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, false, NESTMATE); Class c = lookup.lookupClass(); assertTrue(c.getNestHost() == DefineClassWithClassData.class); assertTrue(c.isHiddenClass()); assertEquals(classData, injectedData(c)); // invoke int test(DefineClassWithClassData o) int x = testInjectedClass(c); assertTrue(x == privMethod()); // dynamic nestmate is not listed in the return array of getNestMembers assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c)); assertTrue(c.isNestmateOf(DefineClassWithClassData.class)); } @Test public void defineStrongClass() throws Throwable { Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, true, STRONG); Class c = lookup.lookupClass(); assertTrue(c.getNestHost() == c); assertTrue(c.isHiddenClass()); } @Test(expectedExceptions = IllegalAccessException.class) public void noPrivateLookupAccess() throws Throwable { Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE); lookup.defineHiddenClassWithClassData(T2_CLASS_BYTES, classData, false, NESTMATE); } @Test public void teleportToNestmate() throws Throwable { Lookup lookup = MethodHandles.lookup() .defineHiddenClassWithClassData(T_CLASS_BYTES, classData, false, NESTMATE); Class c = lookup.lookupClass(); assertTrue(c.getNestHost() == DefineClassWithClassData.class); assertEquals(classData, injectedData(c)); assertTrue(c.isHiddenClass()); // Teleport to a nestmate Lookup lookup2 = MethodHandles.lookup().in(DefineClassWithClassData.class); assertTrue((lookup2.lookupModes() & PRIVATE) != 0); Lookup lc = lookup2.defineHiddenClassWithClassData(T2_CLASS_BYTES, classData, false, NESTMATE); assertTrue(lc.lookupClass().getNestHost() == DefineClassWithClassData.class); assertTrue(lc.lookupClass().isHiddenClass()); } static class ClassByteBuilder { static final String OBJECT_CLS = "java/lang/Object"; static final String MHS_CLS = "java/lang/invoke/MethodHandles"; static final String LIST_SIG = "Ljava/util/List;"; static byte[] classBytes(String classname) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); MethodVisitor mv; FieldVisitor fv; String hostClassName = DefineClassWithClassData.class.getName(); cw.visit(V13, ACC_FINAL, classname, null, OBJECT_CLS, null); { fv = cw.visitField(ACC_STATIC | ACC_FINAL, "data", LIST_SIG, null, null); fv.visitEnd(); } { mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null); mv.visitCode(); Handle bsm = new Handle(H_INVOKESTATIC, MHS_CLS, "classData", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false); ConstantDynamic dynamic = new ConstantDynamic("classdata", LIST_SIG, bsm); mv.visitLdcInsn(dynamic); mv.visitFieldInsn(PUTSTATIC, classname, "data", LIST_SIG); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "test", "(L" + hostClassName + ";)I", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, hostClassName, "privMethod", "()I", false); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "printData", "()V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitFieldInsn(GETSTATIC, classname, "data", LIST_SIG); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); byte[] bytes = cw.toByteArray(); Path p = Paths.get(classname + ".class"); try (OutputStream os = Files.newOutputStream(p)) { os.write(bytes); } catch (IOException e) { throw new UncheckedIOException(e); } return bytes; } } }