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