1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @library /test/lib
  27  * @modules java.base/jdk.internal.org.objectweb.asm
  28  * @build  DefineClassWithClassData
  29  * @run testng/othervm DefineClassWithClassData
  30  */
  31 
  32 import java.io.IOException;
  33 import java.io.OutputStream;
  34 import java.io.UncheckedIOException;
  35 import java.lang.invoke.MethodHandles;
  36 import java.lang.invoke.MethodHandles.Lookup;
  37 import java.lang.reflect.InvocationTargetException;
  38 import java.lang.reflect.Method;
  39 import java.nio.file.Files;
  40 import java.nio.file.Path;
  41 import java.nio.file.Paths;
  42 import java.util.List;
  43 import java.util.stream.Stream;
  44 
  45 import jdk.internal.org.objectweb.asm.*;
  46 import org.testng.annotations.Test;
  47 
  48 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*;
  49 import static java.lang.invoke.MethodHandles.Lookup.PRIVATE;
  50 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  51 import static org.testng.Assert.*;
  52 
  53 public class DefineClassWithClassData {
  54     private static final byte[] T_CLASS_BYTES = ClassByteBuilder.classBytes("T");
  55     private static final byte[] T2_CLASS_BYTES = ClassByteBuilder.classBytes("T2");
  56 
  57     private int privMethod() { return 1234; }
  58 
  59     /*
  60      * invoke int test(DefineClassWithClassData o) method defined in the injected class
  61      */
  62     private int testInjectedClass(Class<?> c) throws Throwable {
  63         try {
  64             Method m = c.getMethod("test", DefineClassWithClassData.class);
  65             return (int) m.invoke(c.newInstance(), this);
  66         } catch (InvocationTargetException e) {
  67             throw e.getCause();
  68         }
  69     }
  70 
  71     /*
  72      * Returns the value of the static final "data" field in the injected class
  73      */
  74     private Object injectedData(Class<?> c) throws Throwable {
  75         return c.getDeclaredField("data").get(null);
  76     }
  77 
  78     private static final List<String> classData = List.of("nestmate", "classdata");
  79 
  80     @Test
  81     public void defineNestMate() throws Throwable {
  82         // define a nestmate
  83         Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, true, NESTMATE);
  84         Class<?> c = lookup.lookupClass();
  85         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
  86         assertEquals(classData, injectedData(c));
  87 
  88         // invoke int test(DefineClassWithClassData o)
  89         int x = testInjectedClass(c);
  90         assertTrue(x == privMethod());
  91 
  92         // dynamic nestmate is not listed in the return array of getNestMembers
  93         assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c));
  94         assertTrue(c.isNestmateOf(DefineClassWithClassData.class));
  95     }
  96 
  97     @Test
  98     public void defineHiddenClass() throws Throwable {
  99         // define a hidden class
 100         Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, false, NESTMATE);
 101         Class<?> c = lookup.lookupClass();
 102         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
 103         assertTrue(c.isHiddenClass());
 104         assertEquals(classData, injectedData(c));
 105 
 106         // invoke int test(DefineClassWithClassData o)
 107         int x = testInjectedClass(c);
 108         assertTrue(x == privMethod());
 109 
 110         // dynamic nestmate is not listed in the return array of getNestMembers
 111         assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c));
 112         assertTrue(c.isNestmateOf(DefineClassWithClassData.class));
 113     }
 114 
 115     @Test
 116     public void defineWeakClass() throws Throwable {
 117         // define a weak class
 118         Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, true, WEAK);
 119         Class<?> c = lookup.lookupClass();
 120         assertTrue(c.getNestHost() == c);
 121         assertTrue(c.isHiddenClass());
 122     }
 123 
 124     @Test(expectedExceptions = IllegalAccessException.class)
 125     public void noPrivateLookupAccess() throws Throwable {
 126         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
 127         lookup.defineHiddenClassWithClassData(T2_CLASS_BYTES, classData, false, NESTMATE);
 128     }
 129 
 130     @Test
 131     public void teleportToNestmate() throws Throwable {
 132         Lookup lookup = MethodHandles.lookup()
 133             .defineHiddenClassWithClassData(T_CLASS_BYTES, classData, false, NESTMATE);
 134         Class<?> c = lookup.lookupClass();
 135         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
 136         assertEquals(classData, injectedData(c));
 137         assertTrue(c.isHiddenClass());
 138 
 139         // Teleport to a nestmate
 140         Lookup lookup2 =  MethodHandles.lookup().in(DefineClassWithClassData.class);
 141         assertTrue((lookup2.lookupModes() & PRIVATE) != 0);
 142         Lookup lc = lookup2.defineHiddenClassWithClassData(T2_CLASS_BYTES, classData, false, NESTMATE);
 143         assertTrue(lc.lookupClass().getNestHost() == DefineClassWithClassData.class);
 144         assertTrue(lc.lookupClass().isHiddenClass());
 145     }
 146 
 147     static class ClassByteBuilder {
 148         static final String OBJECT_CLS = "java/lang/Object";
 149         static final String MHS_CLS = "java/lang/invoke/MethodHandles";
 150         static final String LIST_SIG = "Ljava/util/List;";
 151 
 152         static byte[] classBytes(String classname) {
 153             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 154             MethodVisitor mv;
 155             FieldVisitor fv;
 156 
 157             String hostClassName = DefineClassWithClassData.class.getName();
 158 
 159             cw.visit(V13, ACC_FINAL, classname, null, OBJECT_CLS, null);
 160             {
 161                 fv = cw.visitField(ACC_STATIC | ACC_FINAL, "data", LIST_SIG, null, null);
 162                 fv.visitEnd();
 163             }
 164             {
 165                 mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
 166                 mv.visitCode();
 167 
 168                 Handle bsm = new Handle(H_INVOKESTATIC, MHS_CLS, "classData", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false);
 169                 ConstantDynamic dynamic = new ConstantDynamic("classdata", LIST_SIG, bsm);
 170                 mv.visitLdcInsn(dynamic);
 171                 mv.visitFieldInsn(PUTSTATIC, classname, "data", LIST_SIG);
 172                 mv.visitInsn(RETURN);
 173                 mv.visitMaxs(0, 0);
 174                 mv.visitEnd();
 175             }
 176 
 177             {
 178                 mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 179                 mv.visitCode();
 180                 mv.visitVarInsn(ALOAD, 0);
 181                 mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "<init>", "()V", false);
 182                 mv.visitInsn(RETURN);
 183                 mv.visitMaxs(0, 0);
 184                 mv.visitEnd();
 185             }
 186             {
 187                 mv = cw.visitMethod(ACC_PUBLIC, "test", "(L" + hostClassName + ";)I", null, null);
 188                 mv.visitCode();
 189                 mv.visitVarInsn(ALOAD, 0);
 190                 mv.visitVarInsn(ALOAD, 1);
 191                 mv.visitMethodInsn(INVOKEVIRTUAL, hostClassName, "privMethod", "()I", false);
 192                 mv.visitInsn(IRETURN);
 193                 mv.visitMaxs(0, 0);
 194                 mv.visitEnd();
 195             }
 196 
 197             {
 198                 mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "printData", "()V", null, null);
 199                 mv.visitCode();
 200                 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
 201                 mv.visitFieldInsn(GETSTATIC, classname, "data", LIST_SIG);
 202                 mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
 203                 mv.visitInsn(RETURN);
 204                 mv.visitMaxs(0, 0);
 205                 mv.visitEnd();
 206             }
 207             cw.visitEnd();
 208 
 209             byte[] bytes = cw.toByteArray();
 210             Path p = Paths.get(classname + ".class");
 211             try (OutputStream os = Files.newOutputStream(p)) {
 212                 os.write(bytes);
 213             } catch (IOException e) {
 214                 throw new UncheckedIOException(e);
 215             }
 216             return bytes;
 217         }
 218     }
 219 }
 220 
 221