1 /*
   2  * Copyright (c) 2019, 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  DefineHiddenClassTest
  29  * @run testng/othervm DefineHiddenClassTest
  30  */
  31 
  32 import java.lang.invoke.*;
  33 import java.lang.invoke.MethodHandles.Lookup;
  34 import java.lang.reflect.InvocationTargetException;
  35 import java.lang.reflect.Method;
  36 import java.util.stream.Stream;
  37 
  38 import jdk.internal.org.objectweb.asm.*;
  39 import org.testng.annotations.Test;
  40 
  41 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*;
  42 import static java.lang.invoke.MethodHandles.Lookup.PRIVATE;
  43 
  44 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  45 import static org.testng.Assert.*;
  46 
  47 public class DefineHiddenClassTest {
  48     private static final byte[] bytes = classBytes("Injected");
  49     private static byte[] classBytes(String classname) {
  50         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
  51         MethodVisitor mv;
  52 
  53         cw.visit(V12, ACC_FINAL, classname, null, "java/lang/Object", null);
  54 
  55         {
  56             mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
  57             mv.visitCode();
  58             mv.visitVarInsn(ALOAD, 0);
  59             mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
  60             mv.visitInsn(RETURN);
  61             mv.visitMaxs(0, 0);
  62             mv.visitEnd();
  63         }
  64         {
  65             // access a private member of the nest host class
  66             mv = cw.visitMethod(ACC_PUBLIC, "test", "(LDefineHiddenClassTest;)I", null, null);
  67             mv.visitCode();
  68             mv.visitVarInsn(ALOAD, 0);
  69             mv.visitVarInsn(ALOAD, 1);
  70             mv.visitMethodInsn(INVOKEVIRTUAL, "DefineHiddenClassTest", "privMethod", "()I");
  71             mv.visitInsn(IRETURN);
  72             mv.visitMaxs(0, 0);
  73             mv.visitEnd();
  74         }
  75         cw.visitEnd();
  76 
  77         return cw.toByteArray();
  78     }
  79 
  80     private int privMethod() { return 1234; }
  81 
  82     @Test
  83     public void defineHiddenClass() throws Throwable {
  84         // define a hidden class
  85         Lookup lookup = MethodHandles.lookup();
  86         Class<?> c = lookup.defineHiddenClass(bytes, false, NESTMATE);
  87         System.out.println(c.getName());
  88         assertTrue(c.getNestHost() == DefineHiddenClassTest.class);
  89         assertTrue(c.isHiddenClass());
  90 
  91         // invoke int test(DefineHiddenClassTest o)
  92         int x = testInjectedClass(c);
  93         assertTrue(x == privMethod());
  94 
  95         // dynamic nestmate is not listed in the return array of getNestMembers
  96         assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c));
  97         assertTrue(c.isNestmateOf(DefineHiddenClassTest.class));
  98     }
  99 
 100     @Test
 101     public void defineHiddenClassAsLookup() throws Throwable {
 102         // define a hidden class
 103         Lookup lookup = MethodHandles.lookup().defineHiddenClassAsLookup(bytes, false, NESTMATE);
 104         Class<?> c = lookup.lookupClass();
 105         assertTrue(c.getNestHost() == DefineHiddenClassTest.class);
 106         assertTrue(c.isHiddenClass());
 107 
 108         // invoke int test(DefineHiddenClassTest o) via MethodHandle
 109         MethodHandle ctor = lookup.findConstructor(c, MethodType.methodType(void.class));
 110         MethodHandle mh = lookup.findVirtual(c, "test", MethodType.methodType(int.class, DefineHiddenClassTest.class));
 111         int x = (int)mh.bindTo(ctor.invoke()).invokeExact( this);
 112         assertTrue(x == privMethod());
 113 
 114         // dynamic nestmate is not listed in the return array of getNestMembers
 115         assertTrue(Stream.of(c.getNestHost().getNestMembers()).noneMatch(k -> k == c));
 116         assertTrue(c.isNestmateOf(DefineHiddenClassTest.class));
 117     }
 118 
 119     @Test
 120     public void defineWeakClass() throws Throwable {
 121         // define a weak class
 122         Class<?> c = MethodHandles.lookup().defineHiddenClass(bytes, false, WEAK);
 123         assertTrue(c.getNestHost() == c);
 124         assertTrue(c.isHiddenClass());
 125     }
 126 
 127     @Test(expectedExceptions = IllegalAccessException.class)
 128     public void definePackageAccessClass() throws Throwable {
 129         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
 130         lookup.defineHiddenClass(bytes, false, NESTMATE);
 131     }
 132 
 133     @Test(expectedExceptions = IllegalAccessException.class)
 134     public void noPrivateLookupAccess() throws Throwable {
 135         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
 136         lookup.defineHiddenClass(bytes, false, NESTMATE);
 137     }
 138 
 139     @Test(expectedExceptions = IllegalAccessException.class)
 140     public void teleportToNestmate() throws Throwable {
 141         Class<?> c = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE);
 142         assertTrue(c.getNestHost() == DefineHiddenClassTest.class);
 143         assertTrue(c.isHiddenClass());
 144 
 145         // Teleport to a nestmate
 146         Lookup lookup =  MethodHandles.lookup().in(c);
 147         assertTrue((lookup.lookupModes() & PRIVATE) != 0);
 148         Class<?> c2 = lookup.defineHiddenClass(bytes, false, NESTMATE);
 149         assertTrue(c2.getNestHost() == DefineHiddenClassTest.class);
 150         assertTrue(c2.isHiddenClass());
 151     }
 152 
 153     @Test(expectedExceptions = IllegalArgumentException.class)
 154     public void notSamePackage() throws Throwable {
 155         MethodHandles.lookup().defineHiddenClass(classBytes("p/Injected"), false, NESTMATE);
 156     }
 157 
 158     /*
 159      * invoke int test(DefineHiddenClassTest o) method defined in the injected class
 160      */
 161     private int testInjectedClass(Class<?> c) throws Throwable {
 162         try {
 163             Method m = c.getMethod("test", DefineHiddenClassTest.class);
 164             return (int) m.invoke(c.newInstance(), this);
 165         } catch (InvocationTargetException e) {
 166             throw e.getCause();
 167         }
 168     }
 169 }