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