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().dropLookupMode(PRIVATE); 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().dropLookupMode(PRIVATE); 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 Lookup lookup = lookup().dropLookupMode(PRIVATE); 148 149 Class<?> clazz = lookup.defineClass(classBytes); 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 // p.C1 in one exploded directory 169 Path dir1 = Files.createTempDirectory("classes"); 170 Path p = Files.createDirectory(dir1.resolve("p")); 171 Files.write(p.resolve("C1.class"), generateClass("p.C1")); 172 URL url1 = dir1.toUri().toURL(); 173 174 // p.C2 in another exploded directory 175 Path dir2 = Files.createTempDirectory("classes"); 176 p = Files.createDirectory(dir2.resolve("p")); 177 Files.write(p.resolve("C2.class"), generateClass("p.C2")); 178 URL url2 = dir2.toUri().toURL(); 179 180 // load p.C1 and p.C2 181 ClassLoader loader = new URLClassLoader(new URL[] { url1, url2 }); 182 Class<?> target1 = Class.forName("p.C1", false, loader); 183 Class<?> target2 = Class.forName("p.C2", false, loader); 184 assertTrue(target1.getClassLoader() == loader); 185 assertTrue(target1.getClassLoader() == loader); 186 assertNotEquals(target1.getProtectionDomain(), target2.getProtectionDomain()); 187 188 // protection domain 1 189 Lookup lookup1 = privateLookupIn(target1, lookup()).dropLookupMode(PRIVATE); 190 191 Class<?> clazz = lookup1.defineClass(generateClass("p.Foo")); 192 testSameAbode(clazz, lookup1.lookupClass()); 193 testDiscoverable(clazz, lookup1); 194 195 // protection domain 2 196 Lookup lookup2 = privateLookupIn(target2, lookup()).dropLookupMode(PRIVATE); 197 198 clazz = lookup2.defineClass(generateClass("p.Bar")); 199 testSameAbode(clazz, lookup2.lookupClass()); 200 testDiscoverable(clazz, lookup2); 201 } 202 203 /** 204 * Test defineClass defining a class to the boot loader 205 */ 206 @Test 207 public void testBootLoader() throws Exception { 208 Lookup lookup = privateLookupIn(Thread.class, lookup()).dropLookupMode(PRIVATE); 209 assertTrue(lookup.getClass().getClassLoader() == null); 210 211 Class<?> clazz = lookup.defineClass(generateClass("java.lang.Foo")); 212 assertEquals(clazz.getName(), "java.lang.Foo"); 213 testSameAbode(clazz, Thread.class); 214 testDiscoverable(clazz, lookup); 215 } 216 217 @Test(expectedExceptions = { IllegalArgumentException.class }) 218 public void testWrongPackage() throws Exception { 219 Lookup lookup = lookup().dropLookupMode(PRIVATE); 220 lookup.defineClass(generateClass("other.C")); 221 } 222 223 @Test(expectedExceptions = { IllegalAccessException.class }) 224 public void testNoPackageAccess() throws Exception { 225 Lookup lookup = lookup().dropLookupMode(PACKAGE); 226 lookup.defineClass(generateClass(THIS_PACKAGE + ".C")); 227 } 228 229 @Test(expectedExceptions = { UnsupportedOperationException.class }) 230 public void testHasPrivateAccess() throws Exception { 231 Lookup lookup = lookup(); 232 assertTrue(lookup.hasPrivateAccess()); 233 lookup.defineClass(generateClass(THIS_PACKAGE + ".C")); 234 } 235 236 @Test(expectedExceptions = { ClassFormatError.class }) 237 public void testTruncatedClassFile() throws Exception { 238 Lookup lookup = lookup().dropLookupMode(PRIVATE); 239 lookup.defineClass(new byte[0]); 240 } 241 242 @Test(expectedExceptions = { NullPointerException.class }) 243 public void testNull() throws Exception { 244 Lookup lookup = lookup().dropLookupMode(PRIVATE); 245 lookup.defineClass(null); 246 } 247 248 /** 249 * Generates a class file with the given class name 250 */ 251 byte[] generateClass(String className) { 252 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 253 + ClassWriter.COMPUTE_FRAMES); 254 cw.visit(V1_9, 255 ACC_PUBLIC + ACC_SUPER, 256 className.replace(".", "/"), 257 null, 258 "java/lang/Object", 259 null); 260 261 // <init> 262 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 263 mv.visitVarInsn(ALOAD, 0); 264 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 265 mv.visitInsn(RETURN); 266 mv.visitMaxs(0, 0); 267 mv.visitEnd(); 268 269 cw.visitEnd(); 270 return cw.toByteArray(); 271 } 272 273 /** 274 * Generate a class file with the given class name. The class implements Runnable 275 * with a run method to invokestatic the given targetClass/targetMethod. 276 */ 277 byte[] generateRunner(String className, 278 String targetClass, 279 String targetMethod) throws Exception { 280 281 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 282 + ClassWriter.COMPUTE_FRAMES); 283 cw.visit(V1_9, 284 ACC_PUBLIC + ACC_SUPER, 285 className.replace(".", "/"), 286 null, 287 "java/lang/Object", 288 new String[] { "java/lang/Runnable" }); 289 290 // <init> 291 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 292 mv.visitVarInsn(ALOAD, 0); 293 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 294 mv.visitInsn(RETURN); 295 mv.visitMaxs(0, 0); 296 mv.visitEnd(); 297 298 // run() 299 String tc = targetClass.replace(".", "/"); 300 mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null); 301 mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); 302 mv.visitInsn(RETURN); 303 mv.visitMaxs(0, 0); 304 mv.visitEnd(); 305 306 cw.visitEnd(); 307 return cw.toByteArray(); 308 } 309 310 /** 311 * Generate a class file with the given class name. The class will initializer 312 * to invokestatic the given targetClass/targetMethod. 313 */ 314 byte[] generateClassWithInitializer(String className, 315 String targetClass, 316 String targetMethod) throws Exception { 317 318 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 319 + ClassWriter.COMPUTE_FRAMES); 320 cw.visit(V1_9, 321 ACC_PUBLIC + ACC_SUPER, 322 className.replace(".", "/"), 323 null, 324 "java/lang/Object", 325 null); 326 327 // <init> 328 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 329 mv.visitVarInsn(ALOAD, 0); 330 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 331 mv.visitInsn(RETURN); 332 mv.visitMaxs(0, 0); 333 mv.visitEnd(); 334 335 // <clinit> 336 String tc = targetClass.replace(".", "/"); 337 mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); 338 mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); 339 mv.visitInsn(RETURN); 340 mv.visitMaxs(0, 0); 341 mv.visitEnd(); 342 343 cw.visitEnd(); 344 return cw.toByteArray(); 345 } 346 347 private int nextNumber() { 348 return ++nextNumber; 349 } 350 351 private int nextNumber; 352 }