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