1 /* 2 * Copyright (c) 2017, 2020, 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 import java.nio.file.Paths; 41 42 import jdk.internal.org.objectweb.asm.ClassWriter; 43 import jdk.internal.org.objectweb.asm.MethodVisitor; 44 import static jdk.internal.org.objectweb.asm.Opcodes.*; 45 46 import org.testng.annotations.Test; 47 import static org.testng.Assert.*; 48 49 public 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 @Test(expectedExceptions = { NoClassDefFoundError.class }) 241 public void testLinking() throws Exception { 242 lookup().defineClass(generateNonLinkableClass(THIS_PACKAGE + ".NonLinkableClass")); 243 } 244 245 /** 246 * Generates a class file with the given class name 247 */ 248 byte[] generateClass(String className) { 249 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 250 + ClassWriter.COMPUTE_FRAMES); 251 cw.visit(V9, 252 ACC_PUBLIC + ACC_SUPER, 253 className.replace(".", "/"), 254 null, 255 "java/lang/Object", 256 null); 257 258 // <init> 259 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 260 mv.visitVarInsn(ALOAD, 0); 261 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 262 mv.visitInsn(RETURN); 263 mv.visitMaxs(0, 0); 264 mv.visitEnd(); 265 266 cw.visitEnd(); 267 return cw.toByteArray(); 268 } 269 270 /** 271 * Generate a class file with the given class name. The class implements Runnable 272 * with a run method to invokestatic the given targetClass/targetMethod. 273 */ 274 byte[] generateRunner(String className, 275 String targetClass, 276 String targetMethod) throws Exception { 277 278 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 279 + ClassWriter.COMPUTE_FRAMES); 280 cw.visit(V9, 281 ACC_PUBLIC + ACC_SUPER, 282 className.replace(".", "/"), 283 null, 284 "java/lang/Object", 285 new String[] { "java/lang/Runnable" }); 286 287 // <init> 288 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 289 mv.visitVarInsn(ALOAD, 0); 290 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 291 mv.visitInsn(RETURN); 292 mv.visitMaxs(0, 0); 293 mv.visitEnd(); 294 295 // run() 296 String tc = targetClass.replace(".", "/"); 297 mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null); 298 mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); 299 mv.visitInsn(RETURN); 300 mv.visitMaxs(0, 0); 301 mv.visitEnd(); 302 303 cw.visitEnd(); 304 return cw.toByteArray(); 305 } 306 307 /** 308 * Generate a class file with the given class name. The class will initializer 309 * to invokestatic the given targetClass/targetMethod. 310 */ 311 byte[] generateClassWithInitializer(String className, 312 String targetClass, 313 String targetMethod) throws Exception { 314 315 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 316 + ClassWriter.COMPUTE_FRAMES); 317 cw.visit(V9, 318 ACC_PUBLIC + ACC_SUPER, 319 className.replace(".", "/"), 320 null, 321 "java/lang/Object", 322 null); 323 324 // <init> 325 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 326 mv.visitVarInsn(ALOAD, 0); 327 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 328 mv.visitInsn(RETURN); 329 mv.visitMaxs(0, 0); 330 mv.visitEnd(); 331 332 // <clinit> 333 String tc = targetClass.replace(".", "/"); 334 mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); 335 mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); 336 mv.visitInsn(RETURN); 337 mv.visitMaxs(0, 0); 338 mv.visitEnd(); 339 340 cw.visitEnd(); 341 return cw.toByteArray(); 342 } 343 344 /** 345 * Generates a non-linkable class file with the given class name 346 */ 347 byte[] generateNonLinkableClass(String className) { 348 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 349 + ClassWriter.COMPUTE_FRAMES); 350 cw.visit(V14, 351 ACC_PUBLIC + ACC_SUPER, 352 className.replace(".", "/"), 353 null, 354 "MissingSuperClass", 355 null); 356 357 // <init> 358 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 359 mv.visitVarInsn(ALOAD, 0); 360 mv.visitMethodInsn(INVOKESPECIAL, "MissingSuperClass", "<init>", "()V", false); 361 mv.visitInsn(RETURN); 362 mv.visitMaxs(0, 0); 363 mv.visitEnd(); 364 365 cw.visitEnd(); 366 return cw.toByteArray(); 367 } 368 369 private int nextNumber() { 370 return ++nextNumber; 371 } 372 373 private int nextNumber; 374 }