ba39321902
Reviewed-by: liach
455 lines
16 KiB
Java
455 lines
16 KiB
Java
/*
|
|
* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
import java.lang.classfile.*;
|
|
import java.lang.classfile.attribute.*;
|
|
import java.lang.classfile.constantpool.*;
|
|
import jdk.internal.classfile.impl.BoundAttribute;
|
|
|
|
import java.nio.file.Paths;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
import java.lang.reflect.AccessFlag;
|
|
|
|
/**
|
|
* Base class for tests of inner classes attribute.
|
|
* The scenario of tests:
|
|
* 1. set possible values of class modifiers.
|
|
* 2. according to set class modifiers, a test generates sources
|
|
* and golden data with {@code generateTestCases}.
|
|
* 3. a test loops through all test cases and checks InnerClasses
|
|
* attribute with {@code test}.
|
|
*
|
|
* Example, possible flags for outer class are {@code Modifier.PRIVATE and Modifier.PUBLIC},
|
|
* possible flags for inner class are {@code Modifier.EMPTY}.
|
|
* At the second step the test generates two test cases:
|
|
* 1. public class A {
|
|
* public class B {
|
|
* class C {}
|
|
* }
|
|
* }
|
|
* 2. public class A {
|
|
* private class B {
|
|
* class C {}
|
|
* }
|
|
* }
|
|
*/
|
|
public abstract class InnerClassesTestBase extends TestResult {
|
|
|
|
private Modifier[] outerAccessModifiers = {Modifier.EMPTY, Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC};
|
|
private Modifier[] outerOtherModifiers = {Modifier.EMPTY, Modifier.STATIC, Modifier.FINAL, Modifier.ABSTRACT};
|
|
private Modifier[] innerAccessModifiers = outerAccessModifiers;
|
|
private Modifier[] innerOtherModifiers = outerOtherModifiers;
|
|
private boolean isForbiddenWithoutStaticInOuterMods = false;
|
|
|
|
private ClassType outerClassType;
|
|
private ClassType innerClassType;
|
|
private boolean hasSyntheticClass;
|
|
private String prefix = "";
|
|
private String suffix = "";
|
|
|
|
/**
|
|
* Sets properties.
|
|
*
|
|
* Returns generated list of test cases. Method is called in {@code test()}.
|
|
*/
|
|
public abstract void setProperties();
|
|
|
|
/**
|
|
* Runs the test.
|
|
*
|
|
* @param classToTest expected name of outer class
|
|
* @param skipClasses classes that names should not be checked
|
|
*/
|
|
public void test(String classToTest, String...skipClasses) throws TestFailedException {
|
|
try {
|
|
String testName = getClass().getName();
|
|
List<TestCase> testCases = generateTestCases();
|
|
for (int i = 0; i < testCases.size(); ++i) {
|
|
TestCase test = testCases.get(i);
|
|
String testCaseName = testName + i + ".java";
|
|
addTestCase(testCaseName);
|
|
writeToFileIfEnabled(Paths.get(testCaseName), test.getSource());
|
|
test(classToTest, test, skipClasses);
|
|
}
|
|
} catch (Exception e) {
|
|
addFailure(e);
|
|
} finally {
|
|
checkStatus();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If {@code flag} is {@code true} an outer class can not have static modifier.
|
|
*
|
|
* @param flag if {@code true} the outer class can not have static modifier
|
|
*/
|
|
public void setForbiddenWithoutStaticInOuterMods(boolean flag) {
|
|
isForbiddenWithoutStaticInOuterMods = flag;
|
|
}
|
|
|
|
/**
|
|
* Sets the possible access flags of an outer class.
|
|
*
|
|
* @param mods the possible access flags of an outer class
|
|
*/
|
|
public void setOuterAccessModifiers(Modifier...mods) {
|
|
outerAccessModifiers = mods;
|
|
}
|
|
|
|
/**
|
|
* Sets the possible flags of an outer class.
|
|
*
|
|
* @param mods the possible flags of an outer class
|
|
*/
|
|
public void setOuterOtherModifiers(Modifier...mods) {
|
|
outerOtherModifiers = mods;
|
|
}
|
|
|
|
/**
|
|
* Sets the possible access flags of an inner class.
|
|
*
|
|
* @param mods the possible access flags of an inner class
|
|
*/
|
|
public void setInnerAccessModifiers(Modifier...mods) {
|
|
innerAccessModifiers = mods;
|
|
}
|
|
|
|
/**
|
|
* Sets the possible flags of an inner class.
|
|
*
|
|
* @param mods the possible flags of an inner class
|
|
*/
|
|
public void setInnerOtherModifiers(Modifier...mods) {
|
|
innerOtherModifiers = mods;
|
|
}
|
|
|
|
/**
|
|
* Sets the suffix for the generated source.
|
|
*
|
|
* @param suffix a suffix
|
|
*/
|
|
public void setSuffix(String suffix) {
|
|
this.suffix = suffix;
|
|
}
|
|
|
|
/**
|
|
* Sets the prefix for the generated source.
|
|
*
|
|
* @param prefix a prefix
|
|
*/
|
|
public void setPrefix(String prefix) {
|
|
this.prefix = prefix;
|
|
}
|
|
|
|
/**
|
|
* If {@code true} synthetic class is generated.
|
|
*
|
|
* @param hasSyntheticClass if {@code true} synthetic class is generated
|
|
*/
|
|
public void setHasSyntheticClass(boolean hasSyntheticClass) {
|
|
this.hasSyntheticClass = hasSyntheticClass;
|
|
}
|
|
|
|
/**
|
|
* Sets the inner class type.
|
|
*
|
|
* @param innerClassType the inner class type
|
|
*/
|
|
public void setInnerClassType(ClassType innerClassType) {
|
|
this.innerClassType = innerClassType;
|
|
}
|
|
|
|
/**
|
|
* Sets the outer class type.
|
|
*
|
|
* @param outerClassType the outer class type
|
|
*/
|
|
public void setOuterClassType(ClassType outerClassType) {
|
|
this.outerClassType = outerClassType;
|
|
}
|
|
|
|
private void test(String classToTest, TestCase test, String...skipClasses) {
|
|
printf("Testing :\n%s\n", test.getSource());
|
|
try {
|
|
Map<String, Set<String>> class2Flags = test.getFlags();
|
|
ClassModel cm = readClassFile(compile(getCompileOptions(), test.getSource())
|
|
.getClasses().get(classToTest));
|
|
InnerClassesAttribute innerClasses = cm.findAttribute(Attributes.innerClasses()).orElse(null);
|
|
int count = 0;
|
|
for (Attribute<?> a : cm.attributes()) {
|
|
if (a instanceof InnerClassesAttribute) {
|
|
++count;
|
|
}
|
|
}
|
|
checkEquals(1, count, "Number of inner classes attribute");
|
|
if (!checkNotNull(innerClasses, "InnerClasses attribute should not be null")) {
|
|
return;
|
|
}
|
|
checkEquals(innerClasses.attributeName().stringValue(), "InnerClasses",
|
|
"innerClasses.attribute_name_index");
|
|
// Inner Classes attribute consists of length (2 bytes)
|
|
// and 8 bytes for each inner class's entry.
|
|
checkEquals(((BoundAttribute<?>)innerClasses).payloadLen(),
|
|
2 + 8 * class2Flags.size(), "innerClasses.attribute_length");
|
|
checkEquals(innerClasses.classes().size(),
|
|
class2Flags.size(), "innerClasses.number_of_classes");
|
|
Set<String> visitedClasses = new HashSet<>();
|
|
for (InnerClassInfo e : innerClasses.classes()) {
|
|
String baseName = e.innerClass().asInternalName();
|
|
if (cm.majorVersion() >= 51 && e.innerClass().index() == 0) {
|
|
ClassEntry out = e.outerClass().orElse(null);
|
|
// The outer_class_info_index of sun.tools.classfile will return 0 if it is not a member of a class or interface.
|
|
checkEquals(out == null? 0: out.index(), 0,
|
|
"outer_class_info_index "
|
|
+ "in case of inner_name_index is zero : "
|
|
+ baseName);
|
|
}
|
|
String className = baseName.replaceFirst(".*\\$", "");
|
|
checkTrue(class2Flags.containsKey(className),
|
|
className);
|
|
checkTrue(visitedClasses.add(className),
|
|
"there are no duplicates in attribute : " + className);
|
|
//Convert the Set<string> to Set<AccessFlag>
|
|
Set<AccessFlag> accFlags = class2Flags.get(className).stream()
|
|
.map(str -> AccessFlag.valueOf(str.substring(str.indexOf("_") + 1)))
|
|
.collect(Collectors.toSet());
|
|
checkEquals(e.flags(),
|
|
accFlags,
|
|
"inner_class_access_flags " + className);
|
|
if (!Arrays.asList(skipClasses).contains(className)) {
|
|
checkEquals(
|
|
e.innerClass().asInternalName(),
|
|
classToTest + "$" + className,
|
|
"inner_class_info_index of " + className);
|
|
if (e.outerClass().orElse(null) != null && e.outerClass().get().index() > 0) {
|
|
checkEquals(
|
|
e.outerClass().get().name().stringValue(),
|
|
classToTest,
|
|
"outer_class_info_index of " + className);
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
addFailure(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Methods generates list of test cases. Method generates all possible combinations
|
|
* of acceptable flags for nested inner classes.
|
|
*
|
|
* @return generated list of test cases
|
|
*/
|
|
protected List<TestCase> generateTestCases() {
|
|
setProperties();
|
|
List<TestCase> list = new ArrayList<>();
|
|
|
|
List<List<Modifier>> outerMods = getAllCombinations(outerAccessModifiers, outerOtherModifiers);
|
|
List<List<Modifier>> innerMods = getAllCombinations(innerAccessModifiers, innerOtherModifiers);
|
|
|
|
for (List<Modifier> outerMod : outerMods) {
|
|
if (isForbiddenWithoutStaticInOuterMods && !outerMod.contains(Modifier.STATIC)) {
|
|
continue;
|
|
}
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append("public class InnerClassesSrc {")
|
|
.append(toString(outerMod)).append(' ')
|
|
.append(outerClassType).append(' ')
|
|
.append(prefix).append(' ').append('\n');
|
|
int count = 0;
|
|
Map<String, Set<String>> class2Flags = new HashMap<>();
|
|
List<String> syntheticClasses = new ArrayList<>();
|
|
for (List<Modifier> innerMod : innerMods) {
|
|
++count;
|
|
String privateConstructor = "";
|
|
if (hasSyntheticClass && !innerMod.contains(Modifier.ABSTRACT)) {
|
|
privateConstructor = "private A" + count + "() {}";
|
|
syntheticClasses.add("new A" + count + "();");
|
|
}
|
|
sb.append(toString(innerMod)).append(' ');
|
|
sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor));
|
|
Set<String> flags = getFlags(innerClassType, innerMod);
|
|
class2Flags.put("A" + count, flags);
|
|
}
|
|
if (hasSyntheticClass) {
|
|
// Source to generate synthetic classes
|
|
sb.append(syntheticClasses.stream().collect(Collectors.joining(" ", "{", "}")));
|
|
class2Flags.put("1", new HashSet<>(Arrays.asList("ACC_STATIC", "ACC_SYNTHETIC")));
|
|
}
|
|
sb.append(suffix).append("\n}");
|
|
getAdditionalFlags(class2Flags, outerClassType, outerMod.toArray(new Modifier[outerMod.size()]));
|
|
list.add(new TestCase(sb.toString(), class2Flags));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Methods returns flags which must have type.
|
|
*
|
|
* @param type class, interface, enum or annotation
|
|
* @param mods modifiers
|
|
* @return set of access flags
|
|
*/
|
|
protected Set<String> getFlags(ClassType type, List<Modifier> mods) {
|
|
Set<String> flags = mods.stream()
|
|
.map(Modifier::getString)
|
|
.filter(str -> !str.isEmpty())
|
|
.map(str -> "ACC_" + str.toUpperCase())
|
|
.collect(Collectors.toSet());
|
|
type.addSpecificFlags(flags);
|
|
return flags;
|
|
}
|
|
|
|
protected List<String> getCompileOptions() {
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
private List<List<Modifier>> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) {
|
|
List<List<Modifier>> list = new ArrayList<>();
|
|
for (Modifier access : accessModifiers) {
|
|
for (int i = 0; i < otherModifiers.length; ++i) {
|
|
Modifier mod1 = otherModifiers[i];
|
|
for (int j = i + 1; j < otherModifiers.length; ++j) {
|
|
Modifier mod2 = otherModifiers[j];
|
|
if (isForbidden(mod1, mod2)) {
|
|
continue;
|
|
}
|
|
list.add(Arrays.asList(access, mod1, mod2));
|
|
}
|
|
if (mod1 == Modifier.EMPTY) {
|
|
list.add(Collections.singletonList(access));
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private boolean isForbidden(Modifier mod1, Modifier mod2) {
|
|
return mod1 == Modifier.FINAL && mod2 == Modifier.ABSTRACT
|
|
|| mod1 == Modifier.ABSTRACT && mod2 == Modifier.FINAL;
|
|
}
|
|
|
|
private String toString(List<Modifier> mods) {
|
|
return mods.stream()
|
|
.map(Modifier::getString)
|
|
.filter(s -> !s.isEmpty())
|
|
.collect(Collectors.joining(" "));
|
|
}
|
|
|
|
/**
|
|
* Method is called in generateTestCases().
|
|
* If you need to add additional access flags, you should override this method.
|
|
*
|
|
*
|
|
* @param class2Flags map with flags
|
|
* @param type class, interface, enum or @annotation
|
|
* @param mods modifiers
|
|
*/
|
|
public void getAdditionalFlags(Map<String, Set<String>> class2Flags, ClassType type, Modifier...mods) {
|
|
class2Flags.values().forEach(type::addFlags);
|
|
}
|
|
|
|
public enum ClassType {
|
|
CLASS("class") {
|
|
@Override
|
|
public void addSpecificFlags(Set<String> flags) {
|
|
}
|
|
},
|
|
INTERFACE("interface") {
|
|
@Override
|
|
public void addFlags(Set<String> flags) {
|
|
flags.add("ACC_STATIC");
|
|
flags.add("ACC_PUBLIC");
|
|
}
|
|
|
|
@Override
|
|
public void addSpecificFlags(Set<String> flags) {
|
|
flags.add("ACC_INTERFACE");
|
|
flags.add("ACC_ABSTRACT");
|
|
flags.add("ACC_STATIC");
|
|
}
|
|
},
|
|
ANNOTATION("@interface") {
|
|
@Override
|
|
public void addFlags(Set<String> flags) {
|
|
flags.add("ACC_STATIC");
|
|
flags.add("ACC_PUBLIC");
|
|
}
|
|
|
|
@Override
|
|
public void addSpecificFlags(Set<String> flags) {
|
|
flags.add("ACC_INTERFACE");
|
|
flags.add("ACC_ABSTRACT");
|
|
flags.add("ACC_STATIC");
|
|
flags.add("ACC_ANNOTATION");
|
|
}
|
|
},
|
|
ENUM("enum") {
|
|
@Override
|
|
public void addSpecificFlags(Set<String> flags) {
|
|
flags.add("ACC_ENUM");
|
|
flags.add("ACC_FINAL");
|
|
flags.add("ACC_STATIC");
|
|
}
|
|
},
|
|
OTHER("") {
|
|
@Override
|
|
public void addSpecificFlags(Set<String> flags) {
|
|
}
|
|
};
|
|
|
|
private final String classType;
|
|
|
|
ClassType(String clazz) {
|
|
this.classType = clazz;
|
|
}
|
|
|
|
public abstract void addSpecificFlags(Set<String> flags);
|
|
|
|
public String toString() {
|
|
return classType;
|
|
}
|
|
|
|
public void addFlags(Set<String> set) {
|
|
}
|
|
}
|
|
|
|
public enum Modifier {
|
|
PUBLIC("public"), PRIVATE("private"),
|
|
PROTECTED("protected"), DEFAULT("default"),
|
|
FINAL("final"), ABSTRACT("abstract"),
|
|
STATIC("static"), EMPTY("");
|
|
|
|
private final String str;
|
|
|
|
Modifier(String str) {
|
|
this.str = str;
|
|
}
|
|
|
|
public String getString() {
|
|
return str;
|
|
}
|
|
}
|
|
}
|