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.lang.invoke.MethodHandles;
  33 import java.lang.invoke.MethodHandles.Lookup;
  34 import java.lang.reflect.InvocationTargetException;
  35 import java.lang.reflect.Method;
  36 import java.util.List;
  37 import java.util.stream.Stream;
  38 
  39 import jdk.internal.org.objectweb.asm.*;
  40 import org.testng.annotations.Test;
  41 
  42 import static java.lang.invoke.MethodHandles.Lookup.ClassProperty.*;
  43 import static java.lang.invoke.MethodHandles.Lookup.PRIVATE;
  44 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  45 import static org.testng.Assert.*;
  46 
  47 public class DefineClassWithClassData {
  48 
  49     private int privMethod() { return 1234; }
  50 
  51     /*
  52      * invoke int test(DefineClassWithClassData o) method defined in the injected class
  53      */
  54     private int testInjectedClass(Class<?> c) throws Throwable {
  55         try {
  56             Method m = c.getMethod("test", DefineClassWithClassData.class);
  57             return (int) m.invoke(c.newInstance(), this);
  58         } catch (InvocationTargetException e) {
  59             throw e.getCause();
  60         }
  61     }
  62 
  63     /*
  64      * Returns the value of the static final "data" field in the injected class
  65      */
  66     private Object injectedData(Class<?> c) throws Throwable {
  67         return c.getDeclaredField("data").get(null);
  68     }
  69 
  70     private static final List<String> classData = List.of("nestmate", "classdata");
  71 
  72     @Test
  73     public void defineNestMate() throws Throwable {
  74         // define a nestmate
  75         Lookup lookup = MethodHandles.lookup();
  76         Class<?> c = lookup.defineClassWithClassData(ClassByteBuilder.classBytes("T"), classData, NESTMATE, HIDDEN);
  77         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
  78         assertEquals(classData, injectedData(c));
  79 
  80         // invoke int test(DefineClassWithClassData o)
  81         int x = testInjectedClass(c);
  82         assertTrue(x == privMethod());
  83 
  84         // dynamic nestmate is not listed in the return array of getNestMembers
  85         assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c));
  86         assertTrue(c.isNestmateOf(DefineClassWithClassData.class));
  87     }
  88 
  89     @Test
  90     public void defineHiddenClass() throws Throwable {
  91         // define a hidden class
  92         Lookup lookup = MethodHandles.lookup();
  93         Class<?> c = lookup.defineClassWithClassData(ClassByteBuilder.classBytes("T"), classData, NESTMATE, HIDDEN);
  94         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
  95         assertTrue(c.isHidden());
  96         assertEquals(classData, injectedData(c));
  97 
  98         // invoke int test(DefineClassWithClassData o)
  99         int x = testInjectedClass(c);
 100         assertTrue(x == privMethod());
 101 
 102         // dynamic nestmate is not listed in the return array of getNestMembers
 103         assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c));
 104         assertTrue(c.isNestmateOf(DefineClassWithClassData.class));
 105     }
 106 
 107     @Test
 108     public void defineWeakClass() throws Throwable {
 109         // define a weak class
 110         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
 111         Class<?> c = lookup.defineClassWithClassData(ClassByteBuilder.classBytes("T"), classData, WEAK);
 112         assertTrue(c.getNestHost() == c);
 113         assertTrue(c.isHidden());
 114     }
 115 
 116     @Test(expectedExceptions = IllegalAccessException.class)
 117     public void noPrivateLookupAccess() throws Throwable {
 118         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
 119         lookup.defineClassWithClassData(ClassByteBuilder.classBytes("T2"), classData, NESTMATE, HIDDEN);
 120     }
 121 
 122     @Test(expectedExceptions = IllegalAccessException.class)
 123     public void teleportToNestmate() throws Throwable {
 124         byte[] classBytes = ClassByteBuilder.classBytes("T");
 125         Class<?> c = MethodHandles.lookup()
 126             .defineClassWithClassData(classBytes, classData, NESTMATE, HIDDEN);
 127         assertTrue(c.getNestHost() == DefineClassWithClassData.class);
 128         assertEquals(classData, injectedData(c));
 129         assertTrue(c.isHidden());
 130 
 131         // Teleport to a nestmate
 132         Lookup lookup =  MethodHandles.lookup().in(c);
 133         assertTrue((lookup.lookupModes() & PRIVATE) == 0);
 134         // fail to define a nestmate
 135         lookup.defineClassWithClassData(ClassByteBuilder.classBytes("T2"), classData, NESTMATE, HIDDEN);
 136     }
 137 
 138     static class ClassByteBuilder {
 139         static final String OBJECT_CLS = "java/lang/Object";
 140         static final String STRING_CLS = "java/lang/String";
 141         static final String LIST_CLS = "java/util/List";
 142         static final String MH_CLS = "java/lang/invoke/MethodHandles";
 143         static final String LOOKUP_CLS = "java/lang/invoke/MethodHandles$Lookup";
 144         static final String LOOKUP_SIG = "Ljava/lang/invoke/MethodHandles$Lookup;";
 145         static final String LIST_SIG = "Ljava/util/List;";
 146 
 147         static byte[] classBytes(String classname) throws Exception {
 148             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 149             MethodVisitor mv;
 150             FieldVisitor fv;
 151 
 152             String hostClassName = DefineClassWithClassData.class.getName();
 153 
 154             cw.visit(V11, ACC_FINAL, classname, null, OBJECT_CLS, null);
 155             {
 156                 fv = cw.visitField(ACC_STATIC | ACC_FINAL, "data", LIST_SIG, null, null);
 157                 fv.visitEnd();
 158             }
 159             {
 160                 mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
 161                 mv.visitCode();
 162 
 163                 // set up try block
 164                 Label lTryBlockStart =   new Label();
 165                 Label lTryBlockEnd =     new Label();
 166                 Label lCatchBlockStart = new Label();
 167                 Label lCatchBlockEnd =   new Label();
 168                 mv.visitTryCatchBlock(lTryBlockStart, lTryBlockEnd, lCatchBlockStart, "java/lang/IllegalAccessException");
 169 
 170                 mv.visitLabel(lTryBlockStart);
 171                 mv.visitMethodInsn(INVOKESTATIC, MH_CLS, "lookup", "()" + LOOKUP_SIG);
 172                 mv.visitMethodInsn(INVOKEVIRTUAL, LOOKUP_CLS, "classData", "()Ljava/lang/Object;");
 173                 mv.visitTypeInsn(CHECKCAST, LIST_CLS);
 174                 mv.visitFieldInsn(PUTSTATIC, classname, "data", LIST_SIG);
 175                 mv.visitLabel(lTryBlockEnd);
 176                 mv.visitJumpInsn(GOTO, lCatchBlockEnd);
 177 
 178                 mv.visitLabel(lCatchBlockStart);
 179                 mv.visitVarInsn(ASTORE, 0);
 180                 mv.visitTypeInsn(NEW, "java/lang/Error");
 181                 mv.visitInsn(DUP);
 182                 mv.visitVarInsn(ALOAD, 0);
 183                 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Error", "<init>", "(Ljava/lang/Throwable;)V");
 184                 mv.visitInsn(ATHROW);
 185                 mv.visitLabel(lCatchBlockEnd);
 186                 mv.visitInsn(RETURN);
 187                 mv.visitMaxs(0, 0);
 188                 mv.visitEnd();
 189             }
 190 
 191             {
 192                 mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 193                 mv.visitCode();
 194                 mv.visitVarInsn(ALOAD, 0);
 195                 mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "<init>", "()V");
 196                 mv.visitInsn(RETURN);
 197                 mv.visitMaxs(0, 0);
 198                 mv.visitEnd();
 199             }
 200             {
 201                 mv = cw.visitMethod(ACC_PUBLIC, "test", "(L" + hostClassName + ";)I", null, null);
 202                 mv.visitCode();
 203                 mv.visitVarInsn(ALOAD, 0);
 204                 mv.visitVarInsn(ALOAD, 1);
 205                 mv.visitMethodInsn(INVOKEVIRTUAL, hostClassName, "privMethod", "()I");
 206                 mv.visitInsn(IRETURN);
 207                 mv.visitMaxs(0, 0);
 208                 mv.visitEnd();
 209             }
 210 
 211             {
 212                 mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "printData", "()V", null, null);
 213                 mv.visitCode();
 214                 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
 215                 mv.visitFieldInsn(GETSTATIC, classname, "data", LIST_SIG);
 216                 mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V");
 217                 mv.visitInsn(RETURN);
 218                 mv.visitMaxs(0, 0);
 219                 mv.visitEnd();
 220             }
 221             cw.visitEnd();
 222             return cw.toByteArray();
 223         }
 224     }
 225 }
 226 
 227