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 defineStrongClass() throws Throwable {
 117         Lookup lookup = MethodHandles.lookup().defineHiddenClassWithClassData(T_CLASS_BYTES, classData, true, STRONG);

 118         Class<?> c = lookup.lookupClass();
 119         assertTrue(c.getNestHost() == c);
 120         assertTrue(c.isHiddenClass());
 121     }
 122 
 123     @Test(expectedExceptions = IllegalAccessException.class)
 124     public void noPrivateLookupAccess() throws Throwable {
 125         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
 126         lookup.defineHiddenClassWithClassData(T2_CLASS_BYTES, classData, false, NESTMATE);
 127     }
 128 
 129     @Test
 130     public void teleportToNestmate() throws Throwable {
 131         Lookup lookup = MethodHandles.lookup()
 132             .defineHiddenClassWithClassData(T_CLASS_BYTES, classData, false, NESTMATE);
 133         Class<?> c = lookup.lookupClass();
 134         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
 135         assertEquals(classData, injectedData(c));
 136         assertTrue(c.isHiddenClass());
 137 
 138         // Teleport to a nestmate
 139         Lookup lookup2 =  MethodHandles.lookup().in(DefineClassWithClassData.class);
 140         assertTrue((lookup2.lookupModes() & PRIVATE) != 0);
 141         Lookup lc = lookup2.defineHiddenClassWithClassData(T2_CLASS_BYTES, classData, false, NESTMATE);
 142         assertTrue(lc.lookupClass().getNestHost() == DefineClassWithClassData.class);
 143         assertTrue(lc.lookupClass().isHiddenClass());
 144     }
 145 
 146     static class ClassByteBuilder {
 147         static final String OBJECT_CLS = "java/lang/Object";
 148         static final String MHS_CLS = "java/lang/invoke/MethodHandles";
 149         static final String LIST_SIG = "Ljava/util/List;";
 150 
 151         static byte[] classBytes(String classname) {
 152             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 153             MethodVisitor mv;
 154             FieldVisitor fv;
 155 
 156             String hostClassName = DefineClassWithClassData.class.getName();
 157 
 158             cw.visit(V13, ACC_FINAL, classname, null, OBJECT_CLS, null);
 159             {
 160                 fv = cw.visitField(ACC_STATIC | ACC_FINAL, "data", LIST_SIG, null, null);
 161                 fv.visitEnd();
 162             }
 163             {
 164                 mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
 165                 mv.visitCode();
 166 
 167                 Handle bsm = new Handle(H_INVOKESTATIC, MHS_CLS, "classData", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false);
 168                 ConstantDynamic dynamic = new ConstantDynamic("classdata", LIST_SIG, bsm);
 169                 mv.visitLdcInsn(dynamic);
 170                 mv.visitFieldInsn(PUTSTATIC, classname, "data", LIST_SIG);
 171                 mv.visitInsn(RETURN);
 172                 mv.visitMaxs(0, 0);
 173                 mv.visitEnd();
 174             }
 175 
 176             {
 177                 mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 178                 mv.visitCode();
 179                 mv.visitVarInsn(ALOAD, 0);
 180                 mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "<init>", "()V", false);
 181                 mv.visitInsn(RETURN);
 182                 mv.visitMaxs(0, 0);
 183                 mv.visitEnd();
 184             }
 185             {
 186                 mv = cw.visitMethod(ACC_PUBLIC, "test", "(L" + hostClassName + ";)I", null, null);
 187                 mv.visitCode();
 188                 mv.visitVarInsn(ALOAD, 0);
 189                 mv.visitVarInsn(ALOAD, 1);
 190                 mv.visitMethodInsn(INVOKEVIRTUAL, hostClassName, "privMethod", "()I", false);
 191                 mv.visitInsn(IRETURN);
 192                 mv.visitMaxs(0, 0);
 193                 mv.visitEnd();
 194             }
 195 
 196             {
 197                 mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "printData", "()V", null, null);
 198                 mv.visitCode();
 199                 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
 200                 mv.visitFieldInsn(GETSTATIC, classname, "data", LIST_SIG);
 201                 mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
 202                 mv.visitInsn(RETURN);
 203                 mv.visitMaxs(0, 0);
 204                 mv.visitEnd();
 205             }
 206             cw.visitEnd();
 207 
 208             byte[] bytes = cw.toByteArray();
 209             Path p = Paths.get(classname + ".class");
 210             try (OutputStream os = Files.newOutputStream(p)) {
 211                 os.write(bytes);
 212             } catch (IOException e) {
 213                 throw new UncheckedIOException(e);
 214             }
 215             return bytes;
 216         }
 217     }
 218 }
 219 
 220 
--- EOF ---