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 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 /** 241 * Generates a class file with the given class name 242 */ 243 byte[] generateClass(String className) { 244 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 245 + ClassWriter.COMPUTE_FRAMES); 246 cw.visit(V1_9, 247 ACC_PUBLIC + ACC_SUPER, 248 className.replace(".", "/"), 249 null, 250 "java/lang/Object", 251 null); 252 253 // <init> 254 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 255 mv.visitVarInsn(ALOAD, 0); 256 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 257 mv.visitInsn(RETURN); 258 mv.visitMaxs(0, 0); 259 mv.visitEnd(); 260 261 cw.visitEnd(); 262 return cw.toByteArray(); 263 } 264 265 /** 266 * Generate a class file with the given class name. The class implements Runnable 267 * with a run method to invokestatic the given targetClass/targetMethod. 268 */ 269 byte[] generateRunner(String className, 270 String targetClass, 271 String targetMethod) throws Exception { 272 273 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 274 + ClassWriter.COMPUTE_FRAMES); 275 cw.visit(V1_9, 276 ACC_PUBLIC + ACC_SUPER, 277 className.replace(".", "/"), 278 null, 279 "java/lang/Object", 280 new String[] { "java/lang/Runnable" }); 281 282 // <init> 283 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 284 mv.visitVarInsn(ALOAD, 0); 285 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 286 mv.visitInsn(RETURN); 287 mv.visitMaxs(0, 0); 288 mv.visitEnd(); 289 290 // run() 291 String tc = targetClass.replace(".", "/"); 292 mv = cw.visitMethod(ACC_PUBLIC, "run", "()V", null, null); 293 mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); 294 mv.visitInsn(RETURN); 295 mv.visitMaxs(0, 0); 296 mv.visitEnd(); 297 298 cw.visitEnd(); 299 return cw.toByteArray(); 300 } 301 302 /** 303 * Generate a class file with the given class name. The class will initializer 304 * to invokestatic the given targetClass/targetMethod. 305 */ 306 byte[] generateClassWithInitializer(String className, 307 String targetClass, 308 String targetMethod) throws Exception { 309 310 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS 311 + ClassWriter.COMPUTE_FRAMES); 312 cw.visit(V1_9, 313 ACC_PUBLIC + ACC_SUPER, 314 className.replace(".", "/"), 315 null, 316 "java/lang/Object", 317 null); 318 319 // <init> 320 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); 321 mv.visitVarInsn(ALOAD, 0); 322 mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); 323 mv.visitInsn(RETURN); 324 mv.visitMaxs(0, 0); 325 mv.visitEnd(); 326 327 // <clinit> 328 String tc = targetClass.replace(".", "/"); 329 mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); 330 mv.visitMethodInsn(INVOKESTATIC, tc, targetMethod, "()V", false); 331 mv.visitInsn(RETURN); 332 mv.visitMaxs(0, 0); 333 mv.visitEnd(); 334 335 cw.visitEnd(); 336 return cw.toByteArray(); 337 } 338 339 private int nextNumber() { 340 return ++nextNumber; 341 } 342 343 private int nextNumber; 344 }