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  HiddenNestmateTest
 29  * @run testng/othervm HiddenNestmateTest
 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 import java.util.Arrays;
 38 
 39 import jdk.internal.org.objectweb.asm.*;
 40 import org.testng.annotations.Test;
 41 
 42 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*;
 43 import static java.lang.invoke.MethodHandles.Lookup.*;
 44 
 45 import static jdk.internal.org.objectweb.asm.Opcodes.*;
 46 import static org.testng.Assert.*;
 47 
 48 public class HiddenNestmateTest {
 49     private static final byte[] bytes = classBytes("HiddenInjected");
 50 
 51     private static void assertNestmate(Lookup lookup) {
 52         assertTrue((lookup.lookupModes() & PRIVATE) != 0);
 53         assertTrue((lookup.lookupModes() & MODULE) != 0);
 54 
 55         Class<?> hiddenClass = lookup.lookupClass();
 56         Class<?> nestHost = hiddenClass.getNestHost();
 57         assertTrue(hiddenClass.isHidden());
 58         assertTrue(nestHost == MethodHandles.lookup().lookupClass());
 59 
 60         // hidden nestmate is not listed in the return array of getNestMembers
 61         assertTrue(Stream.of(nestHost.getNestMembers()).noneMatch(k -> k == hiddenClass));
 62         assertTrue(hiddenClass.isNestmateOf(lookup.lookupClass()));
 63         assertTrue(Arrays.equals(hiddenClass.getNestMembers(), nestHost.getNestMembers()));
 64     }
 65 
 66     /*
 67      * Test a hidden class to have no access to private members of another class
 68      */
 69     @Test
 70     public void hiddenClass() throws Throwable {
 71         // define a hidden class
 72         Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false);
 73         Class<?> c = lookup.lookupClass();
 74         assertTrue(lookup.hasFullPrivilegeAccess());
 75         assertTrue((lookup.lookupModes() & ORIGINAL) == ORIGINAL);
 76         assertTrue(c.getNestHost() == c);  // host of its own nest
 77         assertTrue(c.isHidden());
 78 
 79         // invoke int test(HiddenNestmateTest o) via MethodHandle
 80         MethodHandle ctor = lookup.findConstructor(c, MethodType.methodType(void.class));
 81         MethodHandle mh = lookup.findVirtual(c, "test", MethodType.methodType(int.class, HiddenNestmateTest.class));
 82         try {
 83             int x = (int) mh.bindTo(ctor.invoke()).invokeExact(this);
 84             throw new RuntimeException("should fail when accessing HiddenNestmateTest.privMethod()");
 85         } catch (IllegalAccessError e) {}
 86 
 87         // invoke int test(HiddenNestmateTest o)
 88         try {
 89             int x1 = testInjectedClass(c);
 90             throw new RuntimeException("should fail when accessing HiddenNestmateTest.privMethod()");
 91         } catch (IllegalAccessError e) {}
 92     }
 93 
 94     /*
 95      * Test a hidden class to have access to private members of its nestmates
 96      */
 97     @Test
 98     public void hiddenNestmate() throws Throwable {
 99         // define a hidden nestmate class
100         Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE);
101         Class<?> c = lookup.lookupClass();
102         assertNestmate(lookup);
103 
104         // invoke int test(HiddenNestmateTest o) via MethodHandle
105         MethodHandle ctor = lookup.findConstructor(c, MethodType.methodType(void.class));
106         MethodHandle mh = lookup.findVirtual(c, "test", MethodType.methodType(int.class, HiddenNestmateTest.class));
107         int x = (int)mh.bindTo(ctor.invoke()).invokeExact( this);
108         assertTrue(x == privMethod());
109 
110         // invoke int test(HiddenNestmateTest o)
111         int x1 = testInjectedClass(c);
112         assertTrue(x1 == privMethod());
113     }
114 
115     /*
116      * Test a hidden class created with NESTMATE and STRONG option is a nestmate
117      */
118     @Test
119     public void hiddenStrongClass() throws Throwable {
120         // define a hidden class strongly referenced by the class loader
121         Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE, STRONG);
122         assertNestmate(lookup);
123     }
124 
125     /*
126      * Fail to create a hidden class if dropping PRIVATE lookup mode
127      */
128     @Test(expectedExceptions = IllegalAccessException.class)
129     public void noPrivateLookupAccess() throws Throwable {
130         Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.PRIVATE);
131         lookup.defineHiddenClass(bytes, false, NESTMATE);
132     }
133 
134     public void teleportToNestmate() throws Throwable {
135         Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false, NESTMATE);
136         assertNestmate(lookup);
137 
138         // Teleport to a hidden nestmate
139         Lookup lc =  MethodHandles.lookup().in(lookup.lookupClass());
140         assertTrue((lc.lookupModes() & PRIVATE) != 0);
141         assertTrue((lc.lookupModes() & ORIGINAL) == 0);
142 
143         Lookup lc2 = lc.defineHiddenClass(bytes, false, NESTMATE);
144         assertNestmate(lc2);
145     }
146 
147     /*
148      * Fail to create a hidden class in a different package from the lookup class' package
149      */
150     @Test(expectedExceptions = IllegalArgumentException.class)
151     public void notSamePackage() throws Throwable {
152         MethodHandles.lookup().defineHiddenClass(classBytes("p/HiddenInjected"), false, NESTMATE);
153     }
154 
155     /*
156      * invoke int test(HiddenNestmateTest o) method defined in the injected class
157      */
158     private int testInjectedClass(Class<?> c) throws Throwable {
159         try {
160             Method m = c.getMethod("test", HiddenNestmateTest.class);
161             return (int) m.invoke(c.newInstance(), this);
162         } catch (InvocationTargetException e) {
163             throw e.getCause();
164         }
165     }
166 
167     private static byte[] classBytes(String classname) {
168         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
169         MethodVisitor mv;
170 
171         cw.visit(V12, ACC_FINAL, classname, null, "java/lang/Object", null);
172 
173         {
174             mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
175             mv.visitCode();
176             mv.visitVarInsn(ALOAD, 0);
177             mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
178             mv.visitInsn(RETURN);
179             mv.visitMaxs(0, 0);
180             mv.visitEnd();
181         }
182         {
183             // access a private member of the nest host class
184             mv = cw.visitMethod(ACC_PUBLIC, "test", "(LHiddenNestmateTest;)I", null, null);
185             mv.visitCode();
186             mv.visitVarInsn(ALOAD, 0);
187             mv.visitVarInsn(ALOAD, 1);
188             mv.visitMethodInsn(INVOKEVIRTUAL, "HiddenNestmateTest", "privMethod", "()I");
189             mv.visitInsn(IRETURN);
190             mv.visitMaxs(0, 0);
191             mv.visitEnd();
192         }
193         cw.visitEnd();
194 
195         return cw.toByteArray();
196     }
197 
198     private int privMethod() { return 1234; }
199 }