1 /*
2 * Copyright (c) 2014, 2025, 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 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_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 // Use a release before value classes for now.
330 return List.of("--release", "25");
331 }
332
333 private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
334 List<List<Modifier>> list = new ArrayList<>();
335 for (Modifier access : accessModifiers) {
336 for (int i = 0; i < otherModifiers.length; ++i) {
337 Modifier mod1 = otherModifiers[i];
338 for (int j = i + 1; j < otherModifiers.length; ++j) {
339 Modifier mod2 = otherModifiers[j];
340 if (isForbidden(mod1, mod2)) {
341 continue;
342 }
343 list.add(Arrays.asList(access, mod1, mod2));
344 }
345 if (mod1 == Modifier.EMPTY) {
346 list.add(Collections.singletonList(access));
347 }
348 }
349 }
350 return list;
351 }
352
353 private boolean isForbidden(Modifier mod1, Modifier mod2) {
354 return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT
355 || mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL;
356 }
357
358 private String toString(List<Modifier> mods) {
359 return mods.stream()
360 .map(Modifier::getString)
361 .filter(s -> !s.isEmpty())
362 .collect(Collectors.joining(" "));
363 }
364
365 /**
366 * Method is called in generateTestCases().
367 * If you need to add additional access flags, you should override this method.
368 *
369 *
370 * @param class2Flags map with flags
371 * @param type class, interface, enum or @annotation
372 * @param mods modifiers
373 */
374 public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
375 class2Flags.values().forEach(type::addFlags);
376 }
377
378 public enum ClassType {
379 CLASS("class") {
380 @Override
381 public void addSpecificFlags(Set<String> flags) {
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 }
420 },
421 OTHER("") {
422 @Override
423 public void addSpecificFlags(Set<String> flags) {
424 }
425 };
426
427 private final String classType;
428
429 ClassType(String clazz) {
430 this.classType = clazz;
431 }
432
433 public abstract void addSpecificFlags(Set<String> flags);
434
435 public String toString() {
436 return classType;
437 }
438
439 public void addFlags(Set<String> set) {
440 }
441 }
442
443 public enum Modifier {
444 PUBLIC("public"), PRIVATE("private"),
445 PROTECTED("protected"), DEFAULT("default"),
446 FINAL("final"), ABSTRACT("abstract"),
447 STATIC("static"), EMPTY(""),
448 IDENTITY("identity");
449
450 private final String str;
451
452 Modifier(String str) {
453 this.str = str;
454 }
455
456 public String getString() {
457 return str;
458 }
459 }
460 }