1 /* 2 * Copyright (c) 2013, 2018, 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 package vm.runtime.defmeth.shared.builder; 25 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.util.*; 29 30 import nsk.share.Pair; 31 32 import static jdk.internal.org.objectweb.asm.Opcodes.*; 33 34 import nsk.share.TestFailure; 35 import vm.runtime.defmeth.shared.ClassFileGenerator; 36 import vm.runtime.defmeth.shared.Constants; 37 import vm.runtime.defmeth.shared.DefMethTest; 38 import vm.runtime.defmeth.shared.DefMethTestFailure; 39 import vm.runtime.defmeth.shared.ExecutionMode; 40 import vm.runtime.defmeth.shared.executor.GeneratedTest; 41 import vm.runtime.defmeth.shared.MemoryClassLoader; 42 import vm.runtime.defmeth.shared.executor.MHInvokeWithArgsTest; 43 import vm.runtime.defmeth.shared.Printer; 44 import vm.runtime.defmeth.shared.executor.ReflectionTest; 45 import vm.runtime.defmeth.shared.executor.TestExecutor; 46 import vm.runtime.defmeth.shared.Util; 47 48 import vm.runtime.defmeth.shared.data.Clazz; 49 import vm.runtime.defmeth.shared.data.ConcreteClass; 50 import vm.runtime.defmeth.shared.data.ConcreteClassLazyAdapter; 51 import vm.runtime.defmeth.shared.data.Interface; 52 import vm.runtime.defmeth.shared.data.InterfaceLazyAdapter; 53 import vm.runtime.defmeth.shared.data.Tester; 54 55 /** 56 * Builder for test cases. 57 * 58 * Simplifies construction of test cases. 59 * 60 * Example: 61 * <code> 62 * TestBuilder b = new TestBuilder(); 63 * 64 * Interface I = b.intf("I").build(); 65 * ConcreteClazz C = b.clazz("C").implements(I).build(); 66 * 67 * b.test().callSite(I, C, "hashCode","()I").returns(0).build(); 68 * 69 * b.run(); 70 * </code> 71 * 72 * produces 73 * 74 * <code> 75 * interface I {} 76 * 77 * class C implements I {} 78 * 79 * class Test1 { 80 * public void test() { 81 * I i = new C(); 82 * if (c.hashCode() != 0) { 83 * throw new RuntimeException("Expected 0"); 84 * } 85 * } 86 * } 87 * </code> 88 */ 89 public class TestBuilder { 90 // Class file major version 91 // Used for separate compilation scenarios 92 public final int minMajorVer; 93 94 // Additional access flags for all methods 95 public final int accFlags; 96 97 // Redefine classes as part of testing 98 public final boolean redefineClasses; 99 100 // Redefine classes using Retransformation API 101 public final boolean retransformClasses; 102 103 public final ExecutionMode executionMode; 104 105 // GeneratedTest counter which is used to name the tests. 106 private int testNo = 0; 107 108 // List of classes constructed for the testcase so far 109 private List<Clazz> elements = new ArrayList<>(); 110 public List<Clazz> getElements() { 111 return new ArrayList<>(elements); 112 } 113 114 // List of tests constructed as part of the testcase 115 private List<Tester> tests = new ArrayList<>(); 116 117 // Elements under construction 118 private Set<Builder<?>> underConstruction = new LinkedHashSet<>(); 119 120 private DefMethTest testInstance; 121 122 TestBuilder(DefMethTest testInstance, int minMajorVer, int accFlags, 123 boolean redefineClasses, boolean retransformClasses, ExecutionMode executionMode) { 124 this.testInstance = testInstance; 125 this.minMajorVer = minMajorVer; 126 this.accFlags = accFlags; 127 this.redefineClasses = redefineClasses; 128 this.retransformClasses = retransformClasses; 129 this.executionMode = executionMode; 130 } 131 132 /** 133 * Factory method for Interface builder. 134 * 135 * @param name 136 * @return class builder 137 */ 138 public InterfaceBuilder intf(String name) { 139 InterfaceBuilder b = new InterfaceBuilder(this).name(name); 140 underConstruction.add(b); 141 return b; 142 } 143 144 /** 145 * Factory method for Clazz builder. 146 * 147 * @param name 148 * @return class builder 149 */ 150 public ConcreteClassBuilder clazz(String name) { 151 ConcreteClassBuilder b = new ConcreteClassBuilder(this).name(name).ver(minMajorVer); 152 underConstruction.add(b); 153 return b; 154 } 155 156 /** 157 * Factory method for Tester builder. 158 * 159 * @return test builder 160 */ 161 public TesterBuilder test() { 162 TesterBuilder b = new TesterBuilder(++testNo, this); 163 underConstruction.add(b); 164 return b; 165 } 166 167 /** 168 * Find previously constructed class by it's name. 169 * 170 * The method is considered safe: if it fails to find a class, it throws 171 * IllegalArgumentException. 172 * 173 * @param name 174 * @return 175 */ 176 public Clazz lookup(String name) { 177 for (Clazz clz : elements) { 178 if (clz.name().equals(name)) { 179 return clz; 180 } 181 } 182 183 throw new IllegalArgumentException("Unknown element: " + name); 184 } 185 186 /** 187 * Lazy binding of {@code Clazz} instance 188 * 189 * @param name 190 * @return 191 */ 192 public ConcreteClass clazzByName(String name) { 193 return new ConcreteClassLazyAdapter(this, name); 194 } 195 196 /** 197 * Lazy binding of {@code Interface} instance 198 * 199 * @param name 200 * @return 201 */ 202 public Interface intfByName(String name) { 203 return new InterfaceLazyAdapter(this, name); 204 } 205 206 /** 207 * Construct corresponding {@code Clazz} instance for a {@code Class}. 208 * 209 * @param cls 210 * @return 211 */ 212 public Clazz toClazz(Class cls) { 213 String name = cls.getName(); 214 215 if (hasElement(name)) { 216 return lookup(name); 217 } else { 218 return clazz(name).build(); 219 } 220 } 221 222 /** 223 * Construct corresponding {@code ConcreteClass} instance for a {@code Class}. 224 * 225 * Throws {@code IllegalArgumentException} if {@code Class} can't be 226 * represented as {@code ConcreteClass} 227 * 228 * @param cls 229 * @return 230 */ 231 public ConcreteClass toConcreteClass(Class cls) { 232 if (!cls.isInterface()) { 233 return (ConcreteClass)toClazz(cls); 234 } else { 235 throw new IllegalArgumentException(cls.getName()); 236 } 237 } 238 239 /** 240 * Factory method for Method builder. 241 * 242 * @return method builder 243 */ 244 /* package-private */ MethodBuilder method() { 245 return new MethodBuilder(this); 246 } 247 248 /** 249 * Factory method for Data.DefaultMethod builder. 250 * 251 * @param name method name 252 * @return 253 */ 254 public MethodBuilder defaultMethod(String name) { 255 return method().name(name).type(MethodType.DEFAULT); 256 } 257 258 /** 259 * Factory method for Data.AbstractMethod builder. 260 * 261 * @param name 262 * @return 263 */ 264 public MethodBuilder abstractMethod(String name) { 265 return method().name(name).type(MethodType.ABSTRACT); 266 } 267 268 /** 269 * Factory method for Data.ConcreteMethod builder. 270 * 271 * @param name 272 * @return 273 */ 274 public MethodBuilder concreteMethod(String name) { 275 return method().name(name).type(MethodType.CONCRETE); 276 } 277 278 /** 279 * Signal that {@code Builder<?>} instance won't be used anymore. 280 * 281 * @param builder 282 */ 283 /* package-private */ void finishConstruction(Builder<?> builder) { 284 if (underConstruction.contains(builder)) { 285 underConstruction.remove(builder); 286 } else { 287 throw new IllegalStateException(); 288 } 289 } 290 291 /** 292 * Register class with the test builder, so it'll be enumerated during 293 * hierarchy traversals (e.g. class file generation, hierarchy printing). 294 * 295 * @param clz 296 * @return 297 */ 298 public TestBuilder register(Clazz clz) { 299 elements.add(clz); 300 return this; 301 } 302 303 /** 304 * Register class with the test builder as a test, so it'll be enumerated during 305 * hierarchy traversals and executed during test runs. 306 * 307 * @param test 308 * @return 309 */ 310 public TestBuilder register(Tester test) { 311 tests.add(test); 312 return this; 313 } 314 315 /** 316 * Check whether a class with some name has been already constructed. 317 * 318 * @param name 319 * @return whether a class with the same name has been already created 320 */ 321 /* package-private */ boolean hasElement(String name) { 322 for (Clazz clz : elements) { 323 if (clz.name().equals(name)) { 324 return true; 325 } 326 } 327 328 return false; 329 } 330 331 /** 332 * Create all classes and return a class loader which can load them. 333 * 334 * @return class loader instance which loads all generated classes 335 */ 336 public MemoryClassLoader build() { 337 Map<String,byte[]> classes = produce(); 338 339 MemoryClassLoader cl = new MemoryClassLoader(classes); 340 341 return cl; 342 } 343 344 /** 345 * Produce class files for a set of {@code Clazz} instances. 346 * 347 * @return 348 */ 349 public Map<String,byte[]> produce() { 350 if (!underConstruction.isEmpty()) { 351 throw new IllegalStateException("Some of the classes haven't been fully constructed"); 352 } 353 354 List<Clazz> items = new ArrayList<>(); 355 items.addAll(elements); 356 items.addAll(tests); 357 358 return produce(52, items); 359 } 360 361 /** 362 * Produce class files for {@Clazz} instances from {@code elements}. 363 * 364 * @param defaultMajorVer 365 * @param elements 366 * @return 367 */ 368 private Map<String,byte[]> produce(int defaultMajorVer, List<? extends Clazz> elements) { 369 LinkedHashMap<String,byte[]> classes = new LinkedHashMap<>(); 370 371 if (Constants.PRINT_TESTS) { 372 System.out.printf("\nTEST: %s\n\n", Util.getTestName()); 373 } 374 375 for (Clazz clazz : elements) { 376 if (Constants.PRINT_TESTS) { 377 System.out.println(Printer.print(clazz)); 378 } 379 380 if (clazz instanceof Tester && 381 (executionMode == ExecutionMode.REFLECTION || 382 executionMode == ExecutionMode.INVOKE_WITH_ARGS)) { 383 // No need to generate testers for reflection cases 384 continue; 385 } 386 387 Pair<String,byte[]> p = produceClassFile(defaultMajorVer, executionMode, clazz); 388 classes.put(p.first, p.second); 389 } 390 391 if (Constants.PRINT_ASSEMBLY) { 392 System.out.println("\nDISASSEMBLY"); 393 394 for (byte[] cf : classes.values()) { 395 Util.printClassFile(cf); 396 System.out.println(); 397 } 398 } 399 400 if (Constants.ASMIFY) { 401 System.out.println("\nASM"); 402 403 for (byte[] cf : classes.values()) { 404 Util.asmifyClassFile(cf); 405 System.out.println(); 406 } 407 } 408 409 if (Constants.DUMP_CLASSES) { 410 try { 411 File dumpDir = new File("DUMP_CLASS_FILES", testInstance.shortTestName); 412 if (!dumpDir.exists()) { 413 dumpDir.mkdirs(); 414 } 415 416 for (Map.Entry<String,byte[]> entry : classes.entrySet()) { 417 String name = entry.getKey(); 418 byte[] classFile = entry.getValue(); 419 File dumpFile = new File(dumpDir, name+".class"); 420 dumpFile.getParentFile().mkdirs(); 421 try (FileOutputStream file = new FileOutputStream(dumpFile)) { 422 file.write(classFile); 423 } 424 } 425 } catch (Exception e) { 426 throw new Error(e); 427 } 428 } 429 430 return classes; 431 } 432 433 /** 434 * Produce class file from {@code Clazz} instance. 435 * 436 * @param defaultMajorVer 437 * @param clazz 438 * @return 439 */ 440 public static Pair<String,byte[]> produceClassFile(int defaultMajorVer, 441 ExecutionMode execMode, Clazz clazz) { 442 int majorVer = clazz.ver() != 0 ? clazz.ver() : defaultMajorVer; 443 444 ClassFileGenerator cfg = new ClassFileGenerator(majorVer, ACC_PUBLIC, execMode); 445 clazz.visit(cfg); 446 447 byte[] classFile = cfg.getClassFile(); 448 449 return Pair.of(clazz.name(), classFile); 450 } 451 452 /** 453 * Make all preparations for execution. 454 * 455 * @return 456 */ 457 public TestExecutor prepare() { 458 return prepare(build()); 459 } 460 461 private TestExecutor prepare(MemoryClassLoader cl) { 462 if (redefineClasses) { 463 try { 464 cl.modifyClasses(/* retransform = */ false); 465 } catch (TestFailure e) { 466 testInstance.getLog().info(e.getMessage()); 467 throw e; 468 } 469 } 470 471 if (retransformClasses) { 472 try { 473 cl.modifyClasses(/* redefine = */ true); 474 } catch (TestFailure e) { 475 testInstance.getLog().info(e.getMessage()); 476 throw e; 477 } 478 } 479 480 switch (executionMode) { 481 case DIRECT: 482 case INDY: 483 case INVOKE_EXACT: 484 case INVOKE_GENERIC: 485 // Run tests using direct invocation methods 486 return new GeneratedTest(cl, testInstance, tests); 487 case REFLECTION: 488 // Use reflection for testing 489 return new ReflectionTest(cl, this, testInstance, tests); 490 case INVOKE_WITH_ARGS: 491 return new MHInvokeWithArgsTest(cl, testInstance, tests); 492 default: 493 throw new Error("Unknown execution mode: " + executionMode); 494 } 495 } 496 497 public interface LoaderConstructor { 498 public MemoryClassLoader construct(Map< String,byte[]> classFiles); 499 } 500 501 /** 502 * Customize class loader construction. 503 * 504 * @param constructLoader 505 * @return 506 */ 507 public TestExecutor prepare(LoaderConstructor constructLoader) { 508 return prepare(constructLoader.construct(produce())); 509 } 510 511 /** 512 * Construct a test with all necessary classes and execute all tests 513 * from it. 514 */ 515 public void run() { 516 if (tests.isEmpty()) { 517 throw new IllegalStateException("No tests to run"); 518 } 519 520 TestExecutor executor = prepare(); 521 522 List<Pair<Tester,Throwable>> errors = executor.run(); 523 524 if (!errors.isEmpty()) { 525 throw new DefMethTestFailure(errors); 526 } 527 528 } 529 }