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