1 /*
   2  * Copyright (c) 2016, 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 selectionresolution;
  25 
  26 import java.io.File;
  27 import java.io.FileWriter;
  28 import java.util.HashMap;
  29 
  30 /**
  31  * One individual test case.  This class also defines a builder, which
  32  * can be used to build up cases.
  33  */
  34 public class SelectionResolutionTestCase {
  35 
  36     public enum InvokeInstruction {
  37         INVOKESTATIC,
  38         INVOKESPECIAL,
  39         INVOKEINTERFACE,
  40         INVOKEVIRTUAL;
  41     }
  42 
  43     /**
  44      * The class data (includes interface data).
  45      */
  46     public final HashMap<Integer, ClassData> classdata;
  47     /**
  48      * The hierarchy shape.
  49      */
  50     public final HierarchyShape hier;
  51     /**
  52      * The invoke instruction to use.
  53      */
  54     public final InvokeInstruction invoke;
  55     /**
  56      * Which class is the methodref (or interface methodref).
  57      */
  58     public final int methodref;
  59     /**
  60      * Which class is the objectref.
  61      */
  62     public final int objectref;
  63     /**
  64      * Which class is the callsite (this must be a class, not an interface.
  65      */
  66     public final int callsite;
  67     /**
  68      * The expected result.
  69      */
  70     public final Result result;
  71 
  72     private SelectionResolutionTestCase(final HashMap<Integer, ClassData> classdata,
  73                                         final HierarchyShape hier,
  74                                         final InvokeInstruction invoke,
  75                                         final int methodref,
  76                                         final int objectref,
  77                                         final int callsite,
  78                                         final int expected) {
  79         this.classdata = classdata;
  80         this.hier = hier;
  81         this.invoke = invoke;
  82         this.methodref = methodref;
  83         this.objectref = objectref;
  84         this.callsite = callsite;
  85         this.result = Result.is(expected);
  86     }
  87 
  88     private SelectionResolutionTestCase(final HashMap<Integer, ClassData> classdata,
  89                                         final HierarchyShape hier,
  90                                         final InvokeInstruction invoke,
  91                                         final int methodref,
  92                                         final int objectref,
  93                                         final int callsite,
  94                                         final Result result) {
  95         this.classdata = classdata;
  96         this.hier = hier;
  97         this.invoke = invoke;
  98         this.methodref = methodref;
  99         this.objectref = objectref;
 100         this.callsite = callsite;
 101         this.result = result;
 102     }
 103 
 104     private static int currError = 0;
 105 
 106     private String dumpClasses(final ClassConstruct[] classes)
 107         throws Exception {
 108         final String errorDirName = "error_" + currError++;
 109         final File errorDir = new File(errorDirName);
 110         errorDir.mkdirs();
 111         for (int i = 0; i < classes.length; i++) {
 112             classes[i].writeClass(errorDir);
 113         }
 114         try (final FileWriter fos =
 115              new FileWriter(new File(errorDir, "description.txt"))) {
 116             fos.write(this.toString());
 117         }
 118         return errorDirName;
 119     }
 120 
 121     /**
 122      * Run this case, return an error message, or null.
 123      *
 124      * @return An error message, or null if the case succeeded.
 125      */
 126     public String run() {
 127         /* Uncomment this line to print EVERY case */
 128         System.err.println("Running\n" + this);
 129         final ClassBuilder builder =
 130             new ClassBuilder(this, ClassBuilder.ExecutionMode.DIRECT);
 131         try {
 132             final ByteCodeClassLoader bcl = new ByteCodeClassLoader();
 133             final ClassConstruct[] classes = builder.build();
 134 
 135             try {
 136                 bcl.addClasses(classes);
 137                 bcl.loadAll();
 138 
 139                 // Grab the callsite class.
 140                 final Class testclass =
 141                     bcl.findClass(builder.getCallsiteClass().getDottedName());
 142 
 143                 // Get the 'test' method out of it and call it.  The
 144                 // return value tess which class that got selected.
 145                 final java.lang.reflect.Method method =
 146                     testclass.getDeclaredMethod("test");
 147                 final int actual = (Integer) method.invoke(null);
 148                 // Check the result.
 149                 if (!result.complyWith(actual)) {
 150                     final String dump = dumpClasses(classes);
 151                     return "Failed:\n" + this + "\nExpected " + result + " got " + actual + "\nClasses written to " + dump;
 152                 }
 153             } catch (Throwable t) {
 154                 // This catch block is handling exceptions that we
 155                 // might expect to see.
 156                 final Throwable actual = t.getCause();
 157                 if (actual == null) {
 158                     final String dump = dumpClasses(classes);
 159                     System.err.println("Unexpected exception in test\n" + this + "\nClasses written to " + dump);
 160                     throw t;
 161                 } else if (result == null) {
 162                     final String dump = dumpClasses(classes);
 163                     return "Failed:\n" + this + "\nUnexpected exception " + actual + "\nClasses written to " + dump;
 164                 } else if (!result.complyWith(actual)) {
 165                     final String dump = dumpClasses(classes);
 166                     return "Failed:\n" + this + "\nExpected " + this.result + " got " + actual + "\nClasses written to " + dump;
 167                 }
 168             }
 169         } catch(Throwable e) {
 170             throw new RuntimeException(e);
 171         }
 172         return null;
 173     }
 174 
 175     private static void addPackage(final StringBuilder sb,
 176                                   final ClassData cd) {
 177         switch (cd.packageId) {
 178         case SAME: sb.append("Same."); break;
 179         case DIFFERENT: sb.append("Different."); break;
 180         case OTHER: sb.append("Other."); break;
 181         case PLACEHOLDER: sb.append("_."); break;
 182         default: throw new RuntimeException("Impossible case");
 183         }
 184     }
 185 
 186     public String toString() {
 187         final StringBuilder sb = new StringBuilder();
 188         //sb.append("hierarchy:\n" + hier + "\n");
 189         sb.append("invoke:    " + invoke + "\n");
 190         if (methodref != -1) {
 191             if (hier.isClass(methodref)) {
 192                 sb.append("methodref: C" + methodref + "\n");
 193             } else {
 194                 sb.append("methodref: I" + methodref + "\n");
 195             }
 196         }
 197         if (objectref != -1) {
 198             if (hier.isClass(objectref)) {
 199                 sb.append("objectref: C" + objectref + "\n");
 200             } else {
 201                 sb.append("objectref: I" + objectref + "\n");
 202             }
 203         }
 204         if (callsite != -1) {
 205             if (hier.isClass(callsite)) {
 206                 sb.append("callsite: C" + callsite + "\n");
 207             } else {
 208                 sb.append("callsite: I" + callsite + "\n");
 209             }
 210         }
 211         sb.append("result: " + result + "\n");
 212         sb.append("classes:\n\n");
 213 
 214         for(int i = 0; classdata.containsKey(i); i++) {
 215             final ClassData cd = classdata.get(i);
 216 
 217             if (hier.isClass(i)) {
 218                 sb.append("class ");
 219                 addPackage(sb, cd);
 220                 sb.append("C" + i);
 221             } else {
 222                 sb.append("interface ");
 223                 addPackage(sb, cd);
 224                 sb.append("I" + i);
 225             }
 226 
 227             boolean first = true;
 228             for(final int j : hier.classes()) {
 229                 if (hier.inherits(i, j)) {
 230                     if (first) {
 231                         sb.append(" extends C" + j);
 232                     } else {
 233                         sb.append(", C" + j);
 234                     }
 235                 }
 236             }
 237 
 238             first = true;
 239             for(final int j : hier.interfaces()) {
 240                 if (hier.inherits(i, j)) {
 241                     if (first) {
 242                         sb.append(" implements I" + j);
 243                     } else {
 244                         sb.append(", I" + j);
 245                     }
 246                 }
 247             }
 248 
 249             sb.append(cd);
 250         }
 251 
 252         return sb.toString();
 253     }
 254 
 255     /**
 256      * A builder, facilitating building up test cases.
 257      */
 258     public static class Builder {
 259         /**
 260          * A map from class (or interface) id's to ClassDatas
 261          */
 262         public final HashMap<Integer, ClassData> classdata;
 263         /**
 264          * The hierarchy shape.
 265          */
 266         public final HierarchyShape hier;
 267         /**
 268          * Which invoke instruction to use.
 269          */
 270         public InvokeInstruction invoke;
 271         /**
 272          * The id of the methodref (or interface methodref).
 273          */
 274         public int methodref = -1;
 275         /**
 276          * The id of the object ref.  Note that for the generator
 277          * framework to work, this must be set to something.  If an
 278          * objectref isn't used, just set it to the methodref.
 279          */
 280         public int objectref = -1;
 281         /**
 282          * The id of the callsite.
 283          */
 284         public int callsite = -1;
 285         /**
 286          * The id of the expected result.  This is used to store the
 287          * expected resolution result.
 288          */
 289         public int expected;
 290         /**
 291          * The expected result.  This needs to be set before the final
 292          * test case is built.
 293          */
 294         public Result result;
 295 
 296         /**
 297          * Create an empty Builder object.
 298          */
 299         public Builder() {
 300             classdata = new HashMap<>();
 301             hier = new HierarchyShape();
 302         }
 303 
 304         private Builder(final HashMap<Integer, ClassData> classdata,
 305                         final HierarchyShape hier,
 306                         final InvokeInstruction invoke,
 307                         final int methodref,
 308                         final int objectref,
 309                         final int callsite,
 310                         final int expected,
 311                         final Result result) {
 312             this.classdata = classdata;
 313             this.hier = hier;
 314             this.invoke = invoke;
 315             this.methodref = methodref;
 316             this.objectref = objectref;
 317             this.callsite = callsite;
 318             this.expected = expected;
 319             this.result = result;
 320         }
 321 
 322         private Builder(final Builder other) {
 323             this((HashMap<Integer, ClassData>) other.classdata.clone(),
 324                  other.hier.copy(), other.invoke, other.methodref, other.objectref,
 325                  other.callsite, other.expected, other.result);
 326         }
 327 
 328         public SelectionResolutionTestCase build() {
 329             if (result != null) {
 330                 return new SelectionResolutionTestCase(classdata, hier, invoke,
 331                                                        methodref, objectref,
 332                                                        callsite, result);
 333             } else {
 334                 return new SelectionResolutionTestCase(classdata, hier, invoke,
 335                                                        methodref, objectref,
 336                                                        callsite, expected);
 337             }
 338         }
 339 
 340         /**
 341          * Set the expected result.
 342          */
 343         public void setResult(final Result result) {
 344             this.result = result;
 345         }
 346 
 347         /**
 348          * Add a class, and return its id.
 349          *
 350          * @return The new class' id.
 351          */
 352         public int addClass(final ClassData data) {
 353             final int id = hier.addClass();
 354             classdata.put(id, data);
 355             return id;
 356         }
 357 
 358         /**
 359          * Add an interface, and return its id.
 360          *
 361          * @return The new class' id.
 362          */
 363         public int addInterface(final ClassData data) {
 364             final int id = hier.addInterface();
 365             classdata.put(id, data);
 366             return id;
 367         }
 368 
 369         /**
 370          * Make a copy of this builder.
 371          */
 372         public Builder copy() {
 373             return new Builder(this);
 374         }
 375 
 376         public String toString() {
 377             final StringBuilder sb = new StringBuilder();
 378             //sb.append("hierarchy:\n" + hier + "\n");
 379             sb.append("invoke:    " + invoke + "\n");
 380             if (methodref != -1) {
 381                 if (hier.isClass(methodref)) {
 382                     sb.append("methodref: C" + methodref + "\n");
 383                 } else {
 384                     sb.append("methodref: I" + methodref + "\n");
 385                 }
 386             }
 387             if (objectref != -1) {
 388                 if (hier.isClass(objectref)) {
 389                     sb.append("objectref: C" + objectref + "\n");
 390                 } else {
 391                     sb.append("objectref: I" + objectref + "\n");
 392                 }
 393             }
 394             if (callsite != -1) {
 395                 if (hier.isClass(callsite)) {
 396                     sb.append("callsite: C" + callsite + "\n");
 397                 } else {
 398                     sb.append("callsite: I" + callsite + "\n");
 399                 }
 400             }
 401             if (expected != -1) {
 402                 if (hier.isClass(expected)) {
 403                     sb.append("expected: C" + expected + "\n");
 404                 } else {
 405                     sb.append("expected: I" + expected + "\n");
 406                 }
 407             }
 408             sb.append("result: " + result + "\n");
 409             sb.append("classes:\n\n");
 410 
 411             for(int i = 0; classdata.containsKey(i); i++) {
 412                 final ClassData cd = classdata.get(i);
 413 
 414                 if (hier.isClass(i)) {
 415                     sb.append("class ");
 416                     addPackage(sb, cd);
 417                     sb.append("C" + i);
 418                 } else {
 419                     sb.append("interface ");
 420                     addPackage(sb, cd);
 421                     sb.append("I" + i);
 422                 }
 423 
 424                 boolean first = true;
 425                 for(final int j : hier.classes()) {
 426                     if (hier.inherits(i, j)) {
 427                         if (first) {
 428                             sb.append(" extends C" + j);
 429                         } else {
 430                             sb.append(", C" + j);
 431                         }
 432                     }
 433                 }
 434 
 435                 first = true;
 436                 for(final int j : hier.interfaces()) {
 437                     if (hier.inherits(i, j)) {
 438                         if (first) {
 439                             sb.append(" implements I" + j);
 440                         } else {
 441                             sb.append(", I" + j);
 442                         }
 443                     }
 444                 }
 445 
 446                 sb.append(cd);
 447             }
 448 
 449             return sb.toString();
 450         }
 451     }
 452 }