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 }