mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-20 11:04:34 +02:00
8042931: Implement classfile tests for EnclosingMethod attribute
Reviewed-by: jjg, shurailine, anazarov
This commit is contained in:
parent
ed9c1bb743
commit
9130b22f3f
8 changed files with 631 additions and 109 deletions
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8042931
|
||||
* @summary Checking EnclosingMethod attribute of anonymous/local class.
|
||||
* @library /tools/lib /tools/javac/lib ../lib
|
||||
* @build EnclosingMethodTest TestBase TestResult InMemoryFileManager ToolBox
|
||||
* @run main EnclosingMethodTest
|
||||
*/
|
||||
|
||||
import com.sun.tools.classfile.Attribute;
|
||||
import com.sun.tools.classfile.ClassFile;
|
||||
import com.sun.tools.classfile.EnclosingMethod_attribute;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The test checks the enclosing method attribute of anonymous/local classes.
|
||||
* The top-level class contains the anonymous and local classes to be tested. The test examines
|
||||
* each inner class and determine whether the class should have the EnclosingMethod attribute or not.
|
||||
* Golden information about enclosing methods are held in annotation {@code ExpectedEnclosingMethod}.
|
||||
*
|
||||
* The test assumes that a class must have the EnclosingMethod attribute if the class is annotated or
|
||||
* if its parent class is annotated in case of anonymous class. In addition, classes
|
||||
* named {@code VariableInitializer} are introduced to test variable initializer cases. These classes
|
||||
* must not have the enclosing method attribute, but its anonymous derived class must.
|
||||
* After classification of classes, the test checks whether classes contain the correct enclosing
|
||||
* method attribute in case of anonymous/local class, or checks whether classes do not contain
|
||||
* the EnclosingMethod attribute, otherwise.
|
||||
*
|
||||
* Test cases:
|
||||
* top-level class as enclosing class:
|
||||
* 1. anonymous and local classes in static initializer;
|
||||
* 2. anonymous and local classes in instance initializer;
|
||||
* 3. anonymous and local classes in lambda;
|
||||
* 4. anonymous and local classes in constructor;
|
||||
* 5. anonymous and local classes in method;
|
||||
* 6. static and instance variable initializer.
|
||||
*
|
||||
* inner class as enclosing class:
|
||||
* 1. anonymous and local classes in static initializer;
|
||||
* 2. anonymous and local classes in instance initializer;
|
||||
* 3. anonymous and local classes in lambda;
|
||||
* 4. anonymous and local classes in constructor;
|
||||
* 5. anonymous and local classes in method;
|
||||
* 6. static and instance variable initializer.
|
||||
*
|
||||
* enum as enclosing class:
|
||||
* 1. anonymous and local classes in static initializer;
|
||||
* 2. anonymous and local classes in instance initializer;
|
||||
* 3. anonymous and local classes in lambda;
|
||||
* 4. anonymous and local classes in constructor;
|
||||
* 5. anonymous and local classes in method;
|
||||
* 6. static and instance variable initializer.
|
||||
*
|
||||
* interface as enclosing class:
|
||||
* 1. anonymous and local classes in lambda;
|
||||
* 2. anonymous and local classes in static method;
|
||||
* 3. anonymous and local classes in default method;
|
||||
* 4. static variable initializer.
|
||||
*
|
||||
* annotation as enclosing class:
|
||||
* 1. anonymous and local classes in lambda;
|
||||
* 2. static variable initializer.
|
||||
*/
|
||||
public class EnclosingMethodTest extends TestResult {
|
||||
|
||||
private final Map<Class<?>, ExpectedEnclosingMethod> class2EnclosingMethod = new HashMap<>();
|
||||
private final Set<Class<?>> noEnclosingMethod = new HashSet<>();
|
||||
|
||||
public EnclosingMethodTest() throws ClassNotFoundException {
|
||||
Class<EnclosingMethodTest> outerClass = EnclosingMethodTest.class;
|
||||
String outerClassName = outerClass.getSimpleName();
|
||||
File testClasses = getClassDir();
|
||||
FilenameFilter filter = (dir, name) -> name.matches(outerClassName + ".*\\.class");
|
||||
|
||||
for (File file : testClasses.listFiles(filter)) {
|
||||
Class<?> clazz = Class.forName(file.getName().replace(".class", ""));
|
||||
if (clazz.isAnonymousClass()) {
|
||||
// anonymous class cannot be annotated, information is in its parent class.
|
||||
ExpectedEnclosingMethod declaredAnnotation =
|
||||
clazz.getSuperclass().getDeclaredAnnotation(ExpectedEnclosingMethod.class);
|
||||
class2EnclosingMethod.put(clazz, declaredAnnotation);
|
||||
} else {
|
||||
ExpectedEnclosingMethod enclosingMethod = clazz.getDeclaredAnnotation(ExpectedEnclosingMethod.class);
|
||||
// if class is annotated and it does not contain information for variable initializer cases,
|
||||
// then it must have the enclosing method attribute.
|
||||
if (enclosingMethod != null && !clazz.getSimpleName().contains("VariableInitializer")) {
|
||||
class2EnclosingMethod.put(clazz, enclosingMethod);
|
||||
} else {
|
||||
noEnclosingMethod.add(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void test() throws TestFailedException {
|
||||
try {
|
||||
testEnclosingMethodAttribute();
|
||||
testLackOfEnclosingMethodAttribute();
|
||||
} finally {
|
||||
checkStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private void testLackOfEnclosingMethodAttribute() {
|
||||
for (Class<?> clazz : noEnclosingMethod) {
|
||||
try {
|
||||
addTestCase("Class should not have EnclosingMethod attribute : " + clazz);
|
||||
ClassFile classFile = readClassFile(clazz);
|
||||
checkEquals(countEnclosingMethodAttributes(classFile),
|
||||
0l, "number of the EnclosingMethod attribute in the class is zero : "
|
||||
+ classFile.getName());
|
||||
} catch (Exception e) {
|
||||
addFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testEnclosingMethodAttribute() {
|
||||
class2EnclosingMethod.forEach((clazz, enclosingMethod) -> {
|
||||
try {
|
||||
String info = enclosingMethod.info() + " "
|
||||
+ (clazz.isAnonymousClass() ? "anonymous" : "local");
|
||||
addTestCase(info);
|
||||
printf("Testing test case : %s\n", info);
|
||||
ClassFile classFile = readClassFile(clazz);
|
||||
String className = clazz.getName();
|
||||
checkEquals(countEnclosingMethodAttributes(classFile), 1l,
|
||||
"number of the EnclosingMethod attribute in the class is one : "
|
||||
+ clazz);
|
||||
EnclosingMethod_attribute attr = (EnclosingMethod_attribute)
|
||||
classFile.getAttribute(Attribute.EnclosingMethod);
|
||||
|
||||
if (!checkNotNull(attr, "the EnclosingMethod attribute is not null : " + className)) {
|
||||
// stop checking, attr is null. test case failed
|
||||
return;
|
||||
}
|
||||
checkEquals(classFile.constant_pool.getUTF8Value(attr.attribute_name_index),
|
||||
"EnclosingMethod",
|
||||
"attribute_name_index of EnclosingMethod attribute in the class : " + className);
|
||||
checkEquals(attr.attribute_length, 4,
|
||||
"attribute_length of EnclosingMethod attribute in the class : " + className);
|
||||
String expectedClassName = enclosingMethod.enclosingClazz().getName();
|
||||
checkEquals(classFile.constant_pool.getClassInfo(attr.class_index).getName(),
|
||||
expectedClassName, String.format(
|
||||
"enclosing class of EnclosingMethod attribute in the class %s is %s",
|
||||
className, expectedClassName));
|
||||
|
||||
String expectedMethodName = enclosingMethod.enclosingMethod();
|
||||
if (expectedMethodName.isEmpty()) {
|
||||
// class does not have an enclosing method
|
||||
checkEquals(attr.method_index, 0, String.format(
|
||||
"enclosing method of EnclosingMethod attribute in the class %s is null", className));
|
||||
} else {
|
||||
String methodName = classFile.constant_pool.getNameAndTypeInfo(attr.method_index).getName();
|
||||
checkTrue(methodName.startsWith(expectedMethodName), String.format(
|
||||
"enclosing method of EnclosingMethod attribute in the class %s" +
|
||||
" is method name %s" +
|
||||
", actual method name is %s",
|
||||
className, expectedMethodName, methodName));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
addFailure(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long countEnclosingMethodAttributes(ClassFile classFile) {
|
||||
return Stream.of(classFile.attributes.attrs)
|
||||
.filter(x -> x instanceof EnclosingMethod_attribute)
|
||||
.count();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectedEnclosingMethod {
|
||||
String info();
|
||||
Class<?> enclosingClazz();
|
||||
String enclosingMethod() default "";
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws ClassNotFoundException, TestFailedException {
|
||||
new EnclosingMethodTest().test();
|
||||
}
|
||||
|
||||
// Test cases: enclosing class is a top-level class
|
||||
static {
|
||||
// anonymous and local classes in static initializer
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingStaticInitialization in EnclosingMethodTest",
|
||||
enclosingClazz = EnclosingMethodTest.class
|
||||
)
|
||||
class EnclosingStaticInitialization {
|
||||
}
|
||||
new EnclosingStaticInitialization() {
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
// anonymous and local classes in instance initializer
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingInitialization in EnclosingMethodTest",
|
||||
enclosingClazz = EnclosingMethodTest.class
|
||||
)
|
||||
class EnclosingInitialization {
|
||||
}
|
||||
new EnclosingInitialization() {
|
||||
};
|
||||
}
|
||||
|
||||
Runnable lambda = () -> {
|
||||
// anonymous and local classes in lambda
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingLambda in EnclosingMethodTest",
|
||||
enclosingMethod = "lambda",
|
||||
enclosingClazz = EnclosingMethodTest.class
|
||||
)
|
||||
class EnclosingLambda {
|
||||
}
|
||||
new EnclosingLambda() {
|
||||
};
|
||||
};
|
||||
|
||||
EnclosingMethodTest(int i) {
|
||||
// anonymous and local classes in constructor
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingConstructor in EnclosingMethodTest",
|
||||
enclosingMethod = "<init>",
|
||||
enclosingClazz = EnclosingMethodTest.class
|
||||
)
|
||||
class EnclosingConstructor {
|
||||
}
|
||||
new EnclosingConstructor() {
|
||||
};
|
||||
}
|
||||
|
||||
void method() {
|
||||
// anonymous and local classes in method
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingMethod in EnclosingMethodTest",
|
||||
enclosingMethod = "method",
|
||||
enclosingClazz = EnclosingMethodTest.class
|
||||
)
|
||||
class EnclosingMethod {
|
||||
}
|
||||
new EnclosingMethod() {
|
||||
};
|
||||
}
|
||||
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "VariableInitializer in EnclosingMethodTest",
|
||||
enclosingClazz = EnclosingMethodTest.class
|
||||
)
|
||||
static class VariableInitializer {
|
||||
}
|
||||
|
||||
// static variable initializer
|
||||
private static final VariableInitializer cvi = new VariableInitializer() {
|
||||
};
|
||||
|
||||
// instance variable initializer
|
||||
private final VariableInitializer ivi = new VariableInitializer() {
|
||||
};
|
||||
|
||||
// Test cases: enclosing class is an inner class
|
||||
public static class notEnclosing01 {
|
||||
static {
|
||||
// anonymous and local classes in static initializer
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingStaticInitialization in notEnclosing01",
|
||||
enclosingClazz = notEnclosing01.class
|
||||
)
|
||||
class EnclosingStaticInitialization {
|
||||
}
|
||||
new EnclosingStaticInitialization() {
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
// anonymous and local classes in instance initializer
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingInitialization in notEnclosing01",
|
||||
enclosingClazz = notEnclosing01.class
|
||||
)
|
||||
class EnclosingInitialization {
|
||||
}
|
||||
new EnclosingInitialization() {
|
||||
};
|
||||
}
|
||||
|
||||
Runnable lambda = () -> {
|
||||
// anonymous and local classes in lambda
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingLambda in notEnclosing01",
|
||||
enclosingMethod = "lambda",
|
||||
enclosingClazz = notEnclosing01.class
|
||||
)
|
||||
class EnclosingLambda {
|
||||
}
|
||||
new EnclosingLambda() {
|
||||
};
|
||||
};
|
||||
|
||||
notEnclosing01() {
|
||||
// anonymous and local classes in constructor
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingConstructor in notEnclosing01",
|
||||
enclosingMethod = "<init>",
|
||||
enclosingClazz = notEnclosing01.class
|
||||
)
|
||||
class EnclosingConstructor {
|
||||
}
|
||||
new EnclosingConstructor() {
|
||||
};
|
||||
}
|
||||
|
||||
void method() {
|
||||
// anonymous and local classes in method
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingMethod in notEnclosing01",
|
||||
enclosingMethod = "method",
|
||||
enclosingClazz = notEnclosing01.class
|
||||
)
|
||||
class EnclosingMethod {
|
||||
}
|
||||
new EnclosingMethod() {
|
||||
};
|
||||
}
|
||||
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "VariableInitializer in notEnclosing01",
|
||||
enclosingClazz = notEnclosing01.class
|
||||
)
|
||||
static class VariableInitializer {
|
||||
}
|
||||
|
||||
// static variable initializer
|
||||
private static final VariableInitializer cvi = new VariableInitializer() {
|
||||
};
|
||||
|
||||
// instance variable initializer
|
||||
private final VariableInitializer ivi = new VariableInitializer() {
|
||||
};
|
||||
}
|
||||
|
||||
// Test cases: enclosing class is an interface
|
||||
public interface notEnclosing02 {
|
||||
Runnable lambda = () -> {
|
||||
// anonymous and local classes in lambda
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingLambda in notEnclosing02",
|
||||
enclosingMethod = "lambda",
|
||||
enclosingClazz = notEnclosing02.class
|
||||
)
|
||||
class EnclosingLambda {
|
||||
}
|
||||
new EnclosingLambda() {
|
||||
};
|
||||
};
|
||||
|
||||
static void staticMethod() {
|
||||
// anonymous and local classes in static method
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingMethod in notEnclosing02",
|
||||
enclosingMethod = "staticMethod",
|
||||
enclosingClazz = notEnclosing02.class
|
||||
)
|
||||
class EnclosingMethod {
|
||||
}
|
||||
new EnclosingMethod() {
|
||||
};
|
||||
}
|
||||
|
||||
default void defaultMethod() {
|
||||
// anonymous and local classes in default method
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingMethod in notEnclosing02",
|
||||
enclosingMethod = "defaultMethod",
|
||||
enclosingClazz = notEnclosing02.class
|
||||
)
|
||||
class EnclosingMethod {
|
||||
}
|
||||
new EnclosingMethod() {
|
||||
};
|
||||
}
|
||||
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "VariableInitializer in notEnclosing02",
|
||||
enclosingClazz = notEnclosing02.class
|
||||
)
|
||||
static class VariableInitializer {
|
||||
}
|
||||
|
||||
// static variable initializer
|
||||
VariableInitializer cvi = new VariableInitializer() {
|
||||
};
|
||||
}
|
||||
|
||||
// Test cases: enclosing class is an enum
|
||||
public enum notEnclosing03 {;
|
||||
|
||||
static {
|
||||
// anonymous and local classes in static initializer
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingStaticInitialization in notEnclosing03",
|
||||
enclosingClazz = notEnclosing03.class
|
||||
)
|
||||
class EnclosingStaticInitialization {
|
||||
}
|
||||
new EnclosingStaticInitialization() {
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
// anonymous and local classes in instance initializer
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingInitialization in notEnclosing03",
|
||||
enclosingClazz = notEnclosing03.class
|
||||
)
|
||||
class EnclosingInitialization {
|
||||
}
|
||||
new EnclosingInitialization() {
|
||||
};
|
||||
}
|
||||
|
||||
Runnable lambda = () -> {
|
||||
// anonymous and local classes in lambda
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingLambda in notEnclosing03",
|
||||
enclosingMethod = "lambda",
|
||||
enclosingClazz = notEnclosing03.class
|
||||
)
|
||||
class EnclosingLambda {
|
||||
}
|
||||
new EnclosingLambda() {
|
||||
};
|
||||
};
|
||||
|
||||
notEnclosing03() {
|
||||
// anonymous and local classes in constructor
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingConstructor in notEnclosing03",
|
||||
enclosingMethod = "<init>",
|
||||
enclosingClazz = notEnclosing03.class
|
||||
)
|
||||
class EnclosingConstructor {
|
||||
}
|
||||
new EnclosingConstructor() {
|
||||
};
|
||||
}
|
||||
|
||||
void method() {
|
||||
// anonymous and local classes in method
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingMethod in notEnclosing03",
|
||||
enclosingMethod = "method",
|
||||
enclosingClazz = notEnclosing03.class
|
||||
)
|
||||
class EnclosingMethod {
|
||||
}
|
||||
new EnclosingMethod() {
|
||||
};
|
||||
}
|
||||
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "VariableInitializer in notEnclosing03",
|
||||
enclosingClazz = notEnclosing03.class
|
||||
)
|
||||
static class VariableInitializer {
|
||||
}
|
||||
|
||||
// static variable initializer
|
||||
private static final VariableInitializer cvi = new VariableInitializer() {
|
||||
};
|
||||
|
||||
// instance variable initializer
|
||||
private final VariableInitializer ivi = new VariableInitializer() {
|
||||
};
|
||||
}
|
||||
|
||||
// Test cases: enclosing class is an annotation
|
||||
public @interface notEnclosing04 {
|
||||
Runnable lambda = () -> {
|
||||
// anonymous and local classes in lambda
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "EnclosingLambda in notEnclosing04",
|
||||
enclosingMethod = "lambda",
|
||||
enclosingClazz = notEnclosing04.class
|
||||
)
|
||||
class EnclosingLambda {
|
||||
}
|
||||
new EnclosingLambda() {
|
||||
};
|
||||
};
|
||||
|
||||
@ExpectedEnclosingMethod(
|
||||
info = "VariableInitializer in notEnclosing04",
|
||||
enclosingClazz = notEnclosing04.class
|
||||
)
|
||||
static class VariableInitializer {
|
||||
}
|
||||
|
||||
// static variable initializer
|
||||
VariableInitializer cvi = new VariableInitializer() {
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue