1 /*
   2  * Copyright (c) 2014, 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 import com.sun.tools.classfile.Attribute;
  25 import com.sun.tools.classfile.ClassFile;
  26 import com.sun.tools.classfile.InnerClasses_attribute;
  27 import com.sun.tools.classfile.InnerClasses_attribute.Info;
  28 
  29 import java.nio.file.Paths;
  30 import java.util.ArrayList;
  31 import java.util.Arrays;
  32 import java.util.Collections;
  33 import java.util.HashMap;
  34 import java.util.HashSet;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.Set;
  38 import java.util.stream.Collectors;
  39 
  40 /**
  41  * Base class for tests of inner classes attribute.
  42  * The scenario of tests:
  43  *   1. set possible values of class modifiers.
  44  *   2. according to set class modifiers, a test generates sources
  45  * and golden data with {@code generateTestCases}.
  46  *   3. a test loops through all test cases and checks InnerClasses
  47  * attribute with {@code test}.
  48  *
  49  * Example, possible flags for outer class are {@code Modifier.PRIVATE and Modifier.PUBLIC},
  50  * possible flags for inner class are {@code Modifier.EMPTY}.
  51  * At the second step the test generates two test cases:
  52  *   1. public class A {
  53  *        public class B {
  54  *          class C {}
  55  *        }
  56  *      }
  57  *   2. public class A {
  58  *        private class B {
  59  *          class C {}
  60  *        }
  61  *      }
  62  */
  63 public abstract class InnerClassesTestBase extends TestResult {
  64 
  65     private Modifier[] outerAccessModifiers = {Modifier.EMPTY, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC};
  66     private Modifier[] outerOtherModifiers = {Modifier.EMPTY, Modifier.STATIC, Modifier.FINAL, Modifier.ABSTRACT};
  67     private Modifier[] innerAccessModifiers = outerAccessModifiers;
  68     private Modifier[] innerOtherModifiers = outerOtherModifiers;
  69     private boolean isForbiddenWithoutStaticInOuterMods = false;
  70 
  71     private ClassType outerClassType;
  72     private ClassType innerClassType;
  73     private boolean hasSyntheticClass;
  74     private String prefix = "";
  75     private String suffix = "";
  76 
  77     /**
  78      * Sets properties.
  79      *
  80      * Returns generated list of test cases. Method is called in {@code test()}.
  81      */
  82     public abstract void setProperties();
  83 
  84     /**
  85      * Runs the test.
  86      *
  87      * @param classToTest expected name of outer class
  88      * @param skipClasses classes that names should not be checked
  89      */
  90     public void test(String classToTest, String...skipClasses) throws TestFailedException {
  91         try {
  92             String testName = getClass().getName();
  93             List<TestCase> testCases = generateTestCases();
  94             for (int i = 0; i < testCases.size(); ++i) {
  95                 TestCase test = testCases.get(i);
  96                 String testCaseName = testName + i + ".java";
  97                 addTestCase(testCaseName);
  98                 writeToFileIfEnabled(Paths.get(testCaseName), test.getSource());
  99                 test(classToTest, test, skipClasses);
 100             }
 101         } catch (Exception e) {
 102             addFailure(e);
 103         } finally {
 104             checkStatus();
 105         }
 106     }
 107 
 108     /**
 109      * If {@code flag} is {@code true} an outer class can not have static modifier.
 110      *
 111      * @param flag if {@code true} the outer class can not have static modifier
 112      */
 113     public void setForbiddenWithoutStaticInOuterMods(boolean flag) {
 114         isForbiddenWithoutStaticInOuterMods = flag;
 115     }
 116 
 117     /**
 118      * Sets the possible access flags of an outer class.
 119      *
 120      * @param mods the possible access flags of an outer class
 121      */
 122     public void setOuterAccessModifiers(Modifier...mods) {
 123         outerAccessModifiers = mods;
 124     }
 125 
 126     /**
 127      * Sets the possible flags of an outer class.
 128      *
 129      * @param mods the possible flags of an outer class
 130      */
 131     public void setOuterOtherModifiers(Modifier...mods) {
 132         outerOtherModifiers = mods;
 133     }
 134 
 135     /**
 136      * Sets the possible access flags of an inner class.
 137      *
 138      * @param mods the possible access flags of an inner class
 139      */
 140     public void setInnerAccessModifiers(Modifier...mods) {
 141         innerAccessModifiers = mods;
 142     }
 143 
 144     /**
 145      * Sets the possible flags of an inner class.
 146      *
 147      * @param mods the possible flags of an inner class
 148      */
 149     public void setInnerOtherModifiers(Modifier...mods) {
 150         innerOtherModifiers = mods;
 151     }
 152 
 153     /**
 154      * Sets the suffix for the generated source.
 155      *
 156      * @param suffix a suffix
 157      */
 158     public void setSuffix(String suffix) {
 159         this.suffix = suffix;
 160     }
 161 
 162     /**
 163      * Sets the prefix for the generated source.
 164      *
 165      * @param prefix a prefix
 166      */
 167     public void setPrefix(String prefix) {
 168         this.prefix = prefix;
 169     }
 170 
 171     /**
 172      * If {@code true} synthetic class is generated.
 173      *
 174      * @param hasSyntheticClass if {@code true} synthetic class is generated
 175      */
 176     public void setHasSyntheticClass(boolean hasSyntheticClass) {
 177         this.hasSyntheticClass = hasSyntheticClass;
 178     }
 179 
 180     /**
 181      * Sets the inner class type.
 182      *
 183      * @param innerClassType the inner class type
 184      */
 185     public void setInnerClassType(ClassType innerClassType) {
 186         this.innerClassType = innerClassType;
 187     }
 188 
 189     /**
 190      * Sets the outer class type.
 191      *
 192      * @param outerClassType the outer class type
 193      */
 194     public void setOuterClassType(ClassType outerClassType) {
 195         this.outerClassType = outerClassType;
 196     }
 197 
 198     private void test(String classToTest, TestCase test, String...skipClasses) {
 199         printf("Testing :\n%s\n", test.getSource());
 200         try {
 201             Map<String, Set<String>> class2Flags = test.getFlags();
 202             ClassFile cf = readClassFile(compile(getCompileOptions(), test.getSource())
 203                     .getClasses().get(classToTest));
 204             InnerClasses_attribute innerClasses = (InnerClasses_attribute)
 205                     cf.getAttribute(Attribute.InnerClasses);
 206             int count = 0;
 207             for (Attribute a : cf.attributes.attrs) {
 208                 if (a instanceof InnerClasses_attribute) {
 209                     ++count;
 210                 }
 211             }
 212             checkEquals(1, count, "Number of inner classes attribute");
 213             if (!checkNotNull(innerClasses, "InnerClasses attribute should not be null")) {
 214                 return;
 215             }
 216             checkEquals(cf.constant_pool.
 217                     getUTF8Info(innerClasses.attribute_name_index).value, "InnerClasses",
 218                     "innerClasses.attribute_name_index");
 219             // Inner Classes attribute consists of length (2 bytes)
 220             // and 8 bytes for each inner class's entry.
 221             checkEquals(innerClasses.attribute_length,
 222                     2 + 8 * class2Flags.size(), "innerClasses.attribute_length");
 223             checkEquals(innerClasses.number_of_classes,
 224                     class2Flags.size(), "innerClasses.number_of_classes");
 225             Set<String> visitedClasses = new HashSet<>();
 226             for (Info e : innerClasses.classes) {
 227                 String baseName = cf.constant_pool.getClassInfo(
 228                         e.inner_class_info_index).getBaseName();
 229                 if (cf.major_version >= 51 && e.inner_name_index == 0) {
 230                     checkEquals(e.outer_class_info_index, 0,
 231                             "outer_class_info_index "
 232                                     + "in case of inner_name_index is zero : "
 233                                     + baseName);
 234                 }
 235                 String className = baseName.replaceFirst(".*\\$", "");
 236                 checkTrue(class2Flags.containsKey(className),
 237                         className);
 238                 checkTrue(visitedClasses.add(className),
 239                         "there are no duplicates in attribute : " + className);
 240                 checkEquals(e.inner_class_access_flags.getInnerClassFlags(),
 241                         class2Flags.get(className),
 242                         "inner_class_access_flags " + className);
 243                 if (!Arrays.asList(skipClasses).contains(className)) {
 244                         checkEquals(
 245                                 cf.constant_pool.getClassInfo(e.inner_class_info_index).getBaseName(),
 246                                 classToTest + "$" + className,
 247                                 "inner_class_info_index of " + className);
 248                     if (e.outer_class_info_index > 0) {
 249                         checkEquals(
 250                                 cf.constant_pool.getClassInfo(e.outer_class_info_index).getName(),
 251                                 classToTest,
 252                                 "outer_class_info_index of " + className);
 253                     }
 254                 }
 255             }
 256         } catch (Exception e) {
 257             addFailure(e);
 258         }
 259     }
 260 
 261     /**
 262      * Methods generates list of test cases. Method generates all possible combinations
 263      * of acceptable flags for nested inner classes.
 264      *
 265      * @return generated list of test cases
 266      */
 267     protected List<TestCase> generateTestCases() {
 268         setProperties();
 269         List<TestCase> list = new ArrayList<>();
 270 
 271         List<List<Modifier>> outerMods = getAllCombinations(outerAccessModifiers, outerOtherModifiers);
 272         List<List<Modifier>> innerMods = getAllCombinations(innerAccessModifiers, innerOtherModifiers);
 273 
 274         for (List<Modifier> outerMod : outerMods) {
 275             if (isForbiddenWithoutStaticInOuterMods && !outerMod.contains(Modifier.STATIC)) {
 276                 continue;
 277             }
 278             StringBuilder sb = new StringBuilder();
 279             sb.append("public class InnerClassesSrc {")
 280                     .append(toString(outerMod)).append(' ')
 281                     .append(outerClassType).append(' ')
 282                     .append(prefix).append(' ').append('\n');
 283             int count = 0;
 284             Map<String, Set<String>> class2Flags = new HashMap<>();
 285             List<String> syntheticClasses = new ArrayList<>();
 286             for (List<Modifier> innerMod : innerMods) {
 287                 ++count;
 288                 String privateConstructor = "";
 289                 if (hasSyntheticClass && !innerMod.contains(Modifier.ABSTRACT)) {
 290                     privateConstructor = "private A" + count + "() {}";
 291                     syntheticClasses.add("new A" + count + "();");
 292                 }
 293                 sb.append(toString(innerMod)).append(' ');
 294                 sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor));
 295                 Set<String> flags = getFlags(innerClassType, innerMod);
 296                 class2Flags.put("A" + count, flags);
 297             }
 298             if (hasSyntheticClass) {
 299                 // Source to generate synthetic classes
 300                 sb.append(syntheticClasses.stream().collect(Collectors.joining(" ", "{", "}")));
 301                 class2Flags.put("1", new HashSet<>(Arrays.asList("ACC_STATIC", "ACC_SYNTHETIC")));
 302             }
 303             sb.append(suffix).append("\n}");
 304             getAdditionalFlags(class2Flags, outerClassType, outerMod.toArray(new Modifier[outerMod.size()]));
 305             list.add(new TestCase(sb.toString(), class2Flags));
 306         }
 307         return list;
 308     }
 309 
 310     /**
 311      * Methods returns flags which must have type.
 312      *
 313      * @param type class, interface, enum or annotation
 314      * @param mods modifiers
 315      * @return set of access flags
 316      */
 317     protected Set<String> getFlags(ClassType type, List<Modifier> mods) {
 318         Set<String> flags = mods.stream()
 319                 .map(Modifier::getString)
 320                 .filter(str -> !str.isEmpty())
 321                 .map(str -> "ACC_" + str.toUpperCase())
 322                 .collect(Collectors.toSet());
 323         type.addSpecificFlags(flags);
 324         return flags;
 325     }
 326 
 327     protected List<String> getCompileOptions() {
 328         return Collections.emptyList();
 329     }
 330 
 331     private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
 332         List<List<Modifier>> list = new ArrayList<>();
 333         for (Modifier access : accessModifiers) {
 334             for (int i = 0; i < otherModifiers.length; ++i) {
 335                 Modifier mod1 = otherModifiers[i];
 336                 for (int j = i + 1; j < otherModifiers.length; ++j) {
 337                     Modifier mod2 = otherModifiers[j];
 338                     if (isForbidden(mod1, mod2)) {
 339                         continue;
 340                     }
 341                     list.add(Arrays.asList(access, mod1, mod2));
 342                 }
 343                 if (mod1 == Modifier.EMPTY) {
 344                     list.add(Collections.singletonList(access));
 345                 }
 346             }
 347         }
 348         return list;
 349     }
 350 
 351     private boolean isForbidden(Modifier mod1, Modifier mod2) {
 352         return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT
 353                 || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL;
 354     }
 355 
 356     private String toString(List<Modifier> mods) {
 357         return mods.stream()
 358                 .map(Modifier::getString)
 359                 .filter(s -> !s.isEmpty())
 360                 .collect(Collectors.joining(" "));
 361     }
 362 
 363     /**
 364      * Method is called in generateTestCases().
 365      * If you need to add additional access flags, you should override this method.
 366      *
 367      *
 368      * @param class2Flags map with flags
 369      * @param type class, interface, enum or @annotation
 370      * @param mods modifiers
 371      */
 372     public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
 373         class2Flags.values().forEach(type::addFlags);
 374     }
 375 
 376     public enum ClassType {
 377         CLASS("class") {
 378             @Override
 379             public void addSpecificFlags(Set<String> flags) {
 380             }
 381         },
 382         INTERFACE("interface") {
 383             @Override
 384             public void addFlags(Set<String> flags) {
 385                 flags.add("ACC_STATIC");
 386                 flags.add("ACC_PUBLIC");
 387             }
 388 
 389             @Override
 390             public void addSpecificFlags(Set<String> flags) {
 391                 flags.add("ACC_INTERFACE");
 392                 flags.add("ACC_ABSTRACT");
 393                 flags.add("ACC_STATIC");
 394             }
 395         },
 396         ANNOTATION("@interface") {
 397             @Override
 398             public void addFlags(Set<String> flags) {
 399                 flags.add("ACC_STATIC");
 400                 flags.add("ACC_PUBLIC");
 401             }
 402 
 403             @Override
 404             public void addSpecificFlags(Set<String> flags) {
 405                 flags.add("ACC_INTERFACE");
 406                 flags.add("ACC_ABSTRACT");
 407                 flags.add("ACC_STATIC");
 408                 flags.add("ACC_ANNOTATION");
 409             }
 410         },
 411         ENUM("enum") {
 412             @Override
 413             public void addSpecificFlags(Set<String> flags) {
 414                 flags.add("ACC_ENUM");
 415                 flags.add("ACC_FINAL");
 416                 flags.add("ACC_STATIC");
 417             }
 418         },
 419         OTHER("") {
 420             @Override
 421             public void addSpecificFlags(Set<String> flags) {
 422             }
 423         };
 424 
 425         private final String classType;
 426 
 427         ClassType(String clazz) {
 428             this.classType = clazz;
 429         }
 430 
 431         public abstract void addSpecificFlags(Set<String> flags);
 432 
 433         public String toString() {
 434             return classType;
 435         }
 436 
 437         public void addFlags(Set<String> set) {
 438         }
 439     }
 440 
 441     public enum Modifier {
 442         PUBLIC("public"), PRIVATE("private"),
 443         PROTECTED("protected"), DEFAULT("default"),
 444         FINAL("final"), ABSTRACT("abstract"),
 445         STATIC("static"), EMPTY("");
 446 
 447         private final String str;
 448 
 449         Modifier(String str) {
 450             this.str = str;
 451         }
 452 
 453         public String getString() {
 454             return str;
 455         }
 456     }
 457 }