1/*
2 * Copyright (c) 2017, 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/* @test
25 * @modules java.base/java.lang:open
26 *          java.base/jdk.internal.org.objectweb.asm
27 * @run testng/othervm test.DefineClassTest
28 * @summary Basic test for java.lang.invoke.MethodHandles.Lookup.defineClass
29 */
30
31package test;
32
33import java.lang.invoke.MethodHandles.Lookup;
34import static java.lang.invoke.MethodHandles.*;
35import static java.lang.invoke.MethodHandles.Lookup.*;
36import java.net.URL;
37import java.net.URLClassLoader;
38import java.nio.file.Files;
39import java.nio.file.Path;
40import java.nio.file.Paths;
41
42import jdk.internal.org.objectweb.asm.ClassWriter;
43import jdk.internal.org.objectweb.asm.MethodVisitor;
44import static jdk.internal.org.objectweb.asm.Opcodes.*;
45
46import org.testng.annotations.Test;
47import static org.testng.Assert.*;
48
49public class DefineClassTest {
50    private static final String THIS_PACKAGE = DefineClassTest.class.getPackageName();
51
52    /**
53     * Test that a class has the same class loader, and is in the same package and
54     * protection domain, as a lookup class.
55     */
56    void testSameAbode(Class<?> clazz, Class<?> lc) {
57        assertTrue(clazz.getClassLoader() == lc.getClassLoader());
58        assertEquals(clazz.getPackageName(), lc.getPackageName());
59        assertTrue(clazz.getProtectionDomain() == lc.getProtectionDomain());
60    }
61
62    /**
63     * Tests that a class is discoverable by name using Class.forName and
64     * lookup.findClass
65     */
66    void testDiscoverable(Class<?> clazz, Lookup lookup) throws Exception {
67        String cn = clazz.getName();
68        ClassLoader loader = clazz.getClassLoader();
69        assertTrue(Class.forName(cn, false, loader) == clazz);
70        assertTrue(lookup.findClass(cn) == clazz);
71    }
72
73    /**
74     * Basic test of defineClass to define a class in the same package as test.
75     */
76    @Test
77    public void testDefineClass() throws Exception {
78        final String CLASS_NAME = THIS_PACKAGE + ".Foo";
79        Lookup lookup = lookup();
80        Class<?> clazz = lookup.defineClass(generateClass(CLASS_NAME));
81
82        // test name
83        assertEquals(clazz.getName(), CLASS_NAME);
84
85        // test loader/package/protection-domain
86        testSameAbode(clazz, lookup.lookupClass());
87
88        // test discoverable
89        testDiscoverable(clazz, lookup);
90
91        // attempt defineClass again
92        try {
93            lookup.defineClass(generateClass(CLASS_NAME));
94            assertTrue(false);
95        } catch (LinkageError expected) { }
96    }
97
98    /**
99     * Test public/package/protected/private access from class defined with defineClass.
100     */
101    @Test
102    public void testAccess() throws Exception {
103        final String THIS_CLASS = this.getClass().getName();
104        final String CLASS_NAME = THIS_PACKAGE + ".Runner";
105        Lookup lookup = lookup();
106
107        // public
108        byte[] classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method1");
109        testInvoke(lookup.defineClass(classBytes));
110
111        // package
112        classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method2");
113        testInvoke(lookup.defineClass(classBytes));
114
115        // protected (same package)
116        classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method3");
117        testInvoke(lookup.defineClass(classBytes));
118
119        // private
120        classBytes = generateRunner(CLASS_NAME + nextNumber(), THIS_CLASS, "method4");
121        Class<?> clazz = lookup.defineClass(classBytes);
122        Runnable r = (Runnable) clazz.newInstance();
123        try {
124            r.run();
125            assertTrue(false);
126        } catch (IllegalAccessError expected) { }
127    }
128
129    public static void method1() { }
130    static void method2() { }
131    protected static void method3() { }
132    private static void method4() { }
133
134    void testInvoke(Class<?> clazz) throws Exception {
135        Object obj = clazz.newInstance();
136        ((Runnable) obj).run();
137    }
138
139    /**
140     * Test that defineClass does not run the class initializer
141     */
142    @Test
143    public void testInitializerNotRun() throws Exception {
144        final String THIS_CLASS = this.getClass().getName();
145        final String CLASS_NAME = THIS_PACKAGE + ".ClassWithClinit";
146
147        byte[] classBytes = generateClassWithInitializer(CLASS_NAME, THIS_CLASS, "fail");
148        Class<?> clazz = lookup().defineClass(classBytes);
149
150        // trigger initializer to run
151        try {
152            clazz.newInstance();
153            assertTrue(false);
154        } catch (ExceptionInInitializerError e) {
155            assertTrue(e.getCause() instanceof IllegalCallerException);
156        }
157    }
158
159    static void fail() { throw new IllegalCallerException(); }
160
161
162    /**
163     * Test defineClass to define classes in a package containing classes with
164     * different protection domains.
165     */
166    @Test
167    public void testTwoProtectionDomains() throws Exception {
168        Path here = Paths.get("");
169
170        // p.C1 in one exploded directory
171        Path dir1 = Files.createTempDirectory(here, "classes");
172        Path p = Files.createDirectory(dir1.resolve("p"));
173        Files.write(p.resolve("C1.class"), generateClass("p.C1"));
174        URL url1 = dir1.toUri().toURL();
175
176        // p.C2 in another exploded directory
177        Path dir2 = Files.createTempDirectory(here, "classes");
178        p = Files.createDirectory(dir2.resolve("p"));
179        Files.write(p.resolve("C2.class"), generateClass("p.C2"));
180        URL url2 = dir2.toUri().toURL();
181
182        // load p.C1 and p.C2
183        ClassLoader loader = new URLClassLoader(new URL[] { url1, url2 });
184        Class<?> target1 = Class.forName("p.C1", false, loader);
185        Class<?> target2 = Class.forName("p.C2", false, loader);
186        assertTrue(target1.getClassLoader() == loader);
187        assertTrue(target1.getClassLoader() == loader);
188        assertNotEquals(target1.getProtectionDomain(), target2.getProtectionDomain());
189
190        // protection domain 1
191        Lookup lookup1 = privateLookupIn(target1, lookup());
192
193        Class<?> clazz = lookup1.defineClass(generateClass("p.Foo"));
194        testSameAbode(clazz, lookup1.lookupClass());
195        testDiscoverable(clazz, lookup1);
196
197        // protection domain 2
198        Lookup lookup2 = privateLookupIn(target2, lookup());
199
200        clazz = lookup2.defineClass(generateClass("p.Bar"));
201        testSameAbode(clazz, lookup2.lookupClass());
202        testDiscoverable(clazz, lookup2);
203    }
204
205    /**
206     * Test defineClass defining a class to the boot loader
207     */
208    @Test
209    public void testBootLoader() throws Exception {
210        Lookup lookup = privateLookupIn(Thread.class, lookup());
211        assertTrue(lookup.getClass().getClassLoader() == null);
212
213        Class<?> clazz = lookup.defineClass(generateClass("java.lang.Foo"));
214        assertEquals(clazz.getName(), "java.lang.Foo");
215        testSameAbode(clazz, Thread.class);
216        testDiscoverable(clazz, lookup);
217    }
218
219    @Test(expectedExceptions = { IllegalArgumentException.class })
220    public void testWrongPackage() throws Exception {
221        lookup().defineClass(generateClass("other.C"));
222    }
223
224    @Test(expectedExceptions = { IllegalAccessException.class })
225    public void testNoPackageAccess() throws Exception {
226        Lookup lookup = lookup().dropLookupMode(PACKAGE);
227        lookup.defineClass(generateClass(THIS_PACKAGE + ".C"));
228    }
229
230    @Test(expectedExceptions = { ClassFormatError.class })
231    public void testTruncatedClassFile() throws Exception {
232        lookup().defineClass(new byte[0]);
233    }
234
235    @Test(expectedExceptions = { NullPointerException.class })
236    public void testNull() throws Exception {
237        lookup().defineClass(null);
238    }
239
240    /**
241     * Generates a class file with the given class name
242     */
243    byte[] generateClass(String className) {
244        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
245                                         + ClassWriter.COMPUTE_FRAMES);
246        cw.visit(V1_9,
247                ACC_PUBLIC + ACC_SUPER,
248                className.replace(".", "/"),
249                null,
250                "java/lang/Object",
251                null);
252
253        // <init>
254        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
255        mv.visitVarInsn(ALOAD, 0);
256        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
257        mv.visitInsn(RETURN);
258        mv.visitMaxs(0, 0);
259        mv.visitEnd();
260
261        cw.visitEnd();
262        return cw.toByteArray();
263    }
264
265    /**
266     * Generate a class file with the given class name. The class implements Runnable
267     * with a run method to invokestatic the given targetClass/targetMethod.
268     */
269    byte[] generateRunner(String className,
270                          String targetClass,
271                          String targetMethod) throws Exception {
272
273        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
274                                         + ClassWriter.COMPUTE_FRAMES);
275        cw.visit(V1_9,
276                ACC_PUBLIC + ACC_SUPER,
277                className.replace(".", "/"),
278                null,
279                "java/lang/Object",
280                new String[] { "java/lang/Runnable" });
281
282        // <init>
283        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
284        mv.visitVarInsn(ALOAD, 0);
285        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
286        mv.visitInsn(RETURN);
287        mv.visitMaxs(0, 0);
288        mv.visitEnd();
289
290        // run()
291        String tc = targetClass.replace(".", "/");
292        mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null);
293        mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false);
294        mv.visitInsn(RETURN);
295        mv.visitMaxs(0, 0);
296        mv.visitEnd();
297
298        cw.visitEnd();
299        return cw.toByteArray();
300    }
301
302    /**
303     * Generate a class file with the given class name. The class will initializer
304     * to invokestatic the given targetClass/targetMethod.
305     */
306    byte[] generateClassWithInitializer(String className,
307                                        String targetClass,
308                                        String targetMethod) throws Exception {
309
310        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
311                                         + ClassWriter.COMPUTE_FRAMES);
312        cw.visit(V1_9,
313                ACC_PUBLIC + ACC_SUPER,
314                className.replace(".", "/"),
315                null,
316                "java/lang/Object",
317                null);
318
319        // <init>
320        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
321        mv.visitVarInsn(ALOAD, 0);
322        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
323        mv.visitInsn(RETURN);
324        mv.visitMaxs(0, 0);
325        mv.visitEnd();
326
327        // <clinit>
328        String tc = targetClass.replace(".", "/");
329        mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
330        mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false);
331        mv.visitInsn(RETURN);
332        mv.visitMaxs(0, 0);
333        mv.visitEnd();
334
335        cw.visitEnd();
336        return cw.toByteArray();
337    }
338
339    private int nextNumber() {
340        return ++nextNumber;
341    }
342
343    private int nextNumber;
344}
345