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 // Use a release before value classes for now.
326 return List.of("--release", "25");
327 }
328
329 private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
330 List<List<Modifier>> list = new ArrayList<>();
331 for (Modifier access : accessModifiers) {
332 for (int i = 0; i < otherModifiers.length; ++i) {
333 Modifier mod1 = otherModifiers[i];
334 for (int j = i + 1; j < otherModifiers.length; ++j) {
335 Modifier mod2 = otherModifiers[j];
336 if (isForbidden(mod1, mod2)) {
337 continue;
338 }
339 list.add(Arrays.asList(access, mod1, mod2));
340 }
341 if (mod1 == Modifier.EMPTY) {
342 list.add(Collections.singletonList(access));
343 }
344 }
345 }
346 return list;
347 }
348
349 private boolean isForbidden(Modifier mod1, Modifier mod2) {
350 return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT
351 || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL;
352 }
353
354 private String toString(List<Modifier> mods) {
355 return mods.stream()
356 .map(Modifier::getString)
357 .filter(s -> !s.isEmpty())
358 .collect(Collectors.joining(" "));
359 }
360
361 /**
362 * Method is called in generateTestCases().
363 * If you need to add additional access flags, you should override this method.
364 *
365 *
366 * @param class2Flags map with flags
367 * @param type class, interface, enum or @annotation
368 * @param mods modifiers
369 */
370 public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
371 class2Flags.values().forEach(type::addFlags);
372 }
373
374 public enum ClassType {
375 CLASS("class") {
376 @Override
377 public void addSpecificFlags(Set<String> flags) {
378 }
379 },
380 INTERFACE("interface") {
381 @Override
382 public void addFlags(Set<String> flags) {
383 flags.add("ACC_STATIC");
384 flags.add("ACC_PUBLIC");
385 }
386
387 @Override
388 public void addSpecificFlags(Set<String> flags) {
389 flags.add("ACC_INTERFACE");
390 flags.add("ACC_ABSTRACT");
391 flags.add("ACC_STATIC");
392 }
393 },
394 ANNOTATION("@interface") {
395 @Override
396 public void addFlags(Set<String> flags) {
397 flags.add("ACC_STATIC");
398 flags.add("ACC_PUBLIC");
399 }
400
401 @Override
402 public void addSpecificFlags(Set<String> flags) {
403 flags.add("ACC_INTERFACE");
404 flags.add("ACC_ABSTRACT");
405 flags.add("ACC_STATIC");
406 flags.add("ACC_ANNOTATION");
407 }
408 },
409 ENUM("enum") {
410 @Override
411 public void addSpecificFlags(Set<String> flags) {
412 flags.add("ACC_ENUM");
413 flags.add("ACC_FINAL");
414 flags.add("ACC_STATIC");
415 }
416 },
417 OTHER("") {
418 @Override
419 public void addSpecificFlags(Set<String> flags) {
420 }
421 };
422
423 private final String classType;
424
425 ClassType(String clazz) {
426 this.classType = clazz;
427 }
428
429 public abstract void addSpecificFlags(Set<String> flags);
430
431 public String toString() {
432 return classType;
433 }
434
435 public void addFlags(Set<String> set) {
436 }
437 }
438
439 public enum Modifier {
440 PUBLIC("public"), PRIVATE("private"),
441 PROTECTED("protected"), DEFAULT("default"),
442 FINAL("final"), ABSTRACT("abstract"),
443 STATIC("static"), EMPTY(""),
444 IDENTITY("identity");
445
446 private final String str;
447
448 Modifier(String str) {
449 this.str = str;
450 }
451
452 public String getString() {
453 return str;
454 }
455 }
456 }