1 /*
2 * Copyright (c) 2014, 2024, 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.innerClasses()).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().stringValue(), "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 int count = 0;
281 Map<String, Set<String>> class2Flags = new HashMap<>();
282 List<String> syntheticClasses = new ArrayList<>();
283 for (List<Modifier> innerMod : innerMods) {
284 ++count;
285 String privateConstructor = "";
286 if (hasSyntheticClass && !innerMod.contains(Modifier.ABSTRACT)) {
287 privateConstructor = "private A" + count + "() {}";
288 syntheticClasses.add("new A" + count + "();");
289 }
290 sb.append(toString(innerMod)).append(' ');
291 sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor));
292 Set<String> flags = getFlags(innerClassType, innerMod);
293 class2Flags.put("A" + count, flags);
294 }
295 if (hasSyntheticClass) {
296 // Source to generate synthetic classes
297 sb.append(syntheticClasses.stream().collect(Collectors.joining(" ", "{", "}")));
298 class2Flags.put("1", new HashSet<>(Arrays.asList("ACC_STATIC", "ACC_SYNTHETIC")));
299 }
300 sb.append(suffix).append("\n}");
301 getAdditionalFlags(class2Flags, outerClassType, outerMod.toArray(new Modifier[outerMod.size()]));
302 list.add(new TestCase(sb.toString(), class2Flags));
303 }
304 return list;
305 }
306
307 /**
308 * Methods returns flags which must have type.
309 *
310 * @param type class, interface, enum or annotation
311 * @param mods modifiers
312 * @return set of access flags
313 */
314 protected Set<String> getFlags(ClassType type, List<Modifier> mods) {
315 Set<String> flags = mods.stream()
316 .map(Modifier::getString)
317 .filter(str -> !str.isEmpty())
318 .map(str -> "ACC_" + str.toUpperCase())
319 .collect(Collectors.toSet());
320 type.addSpecificFlags(flags);
321 return flags;
322 }
323
324 protected List<String> getCompileOptions() {
325 return Collections.emptyList();
326 }
327
328 private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
329 List<List<Modifier>> list = new ArrayList<>();
330 for (Modifier access : accessModifiers) {
331 for (int i = 0; i < otherModifiers.length; ++i) {
332 Modifier mod1 = otherModifiers[i];
333 for (int j = i + 1; j < otherModifiers.length; ++j) {
334 Modifier mod2 = otherModifiers[j];
335 if (isForbidden(mod1, mod2)) {
336 continue;
337 }
338 list.add(Arrays.asList(access, mod1, mod2));
339 }
340 if (mod1 == Modifier.EMPTY) {
341 list.add(Collections.singletonList(access));
342 }
343 }
344 }
345 return list;
346 }
347
348 private boolean isForbidden(Modifier mod1, Modifier mod2) {
349 return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT
350 || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL;
351 }
352
353 private String toString(List<Modifier> mods) {
354 return mods.stream()
355 .map(Modifier::getString)
356 .filter(s -> !s.isEmpty())
357 .collect(Collectors.joining(" "));
358 }
359
360 /**
361 * Method is called in generateTestCases().
362 * If you need to add additional access flags, you should override this method.
363 *
364 *
365 * @param class2Flags map with flags
366 * @param type class, interface, enum or @annotation
367 * @param mods modifiers
368 */
369 public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
370 class2Flags.values().forEach(type::addFlags);
371 }
372
373 public enum ClassType {
374 CLASS("class") {
375 @Override
376 public void addSpecificFlags(Set<String> flags) {
377 }
378 },
379 INTERFACE("interface") {
380 @Override
381 public void addFlags(Set<String> flags) {
382 flags.add("ACC_STATIC");
383 flags.add("ACC_PUBLIC");
384 }
385
386 @Override
387 public void addSpecificFlags(Set<String> flags) {
388 flags.add("ACC_INTERFACE");
389 flags.add("ACC_ABSTRACT");
390 flags.add("ACC_STATIC");
391 }
392 },
393 ANNOTATION("@interface") {
394 @Override
395 public void addFlags(Set<String> flags) {
396 flags.add("ACC_STATIC");
397 flags.add("ACC_PUBLIC");
398 }
399
400 @Override
401 public void addSpecificFlags(Set<String> flags) {
402 flags.add("ACC_INTERFACE");
403 flags.add("ACC_ABSTRACT");
404 flags.add("ACC_STATIC");
405 flags.add("ACC_ANNOTATION");
406 }
407 },
408 ENUM("enum") {
409 @Override
410 public void addSpecificFlags(Set<String> flags) {
411 flags.add("ACC_ENUM");
412 flags.add("ACC_FINAL");
413 flags.add("ACC_STATIC");
414 }
415 },
416 OTHER("") {
417 @Override
418 public void addSpecificFlags(Set<String> flags) {
419 }
420 };
421
422 private final String classType;
423
424 ClassType(String clazz) {
425 this.classType = clazz;
426 }
427
428 public abstract void addSpecificFlags(Set<String> flags);
429
430 public String toString() {
431 return classType;
432 }
433
434 public void addFlags(Set<String> set) {
435 }
436 }
437
438 public enum Modifier {
439 PUBLIC("public"), PRIVATE("private"),
440 PROTECTED("protected"), DEFAULT("default"),
441 FINAL("final"), ABSTRACT("abstract"),
442 STATIC("static"), EMPTY("");
443
444 private final String str;
445
446 Modifier(String str) {
447 this.str = str;
448 }
449
450 public String getString() {
451 return str;
452 }
453 }
454 }