ba39321902
Reviewed-by: liach
536 lines
20 KiB
Java
536 lines
20 KiB
Java
/*
|
|
* Copyright (c) 2014, 2019, 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 8215470
|
|
* @summary Checking EnclosingMethod attribute of anonymous/local class.
|
|
* @library /tools/lib /tools/javac/lib ../lib
|
|
* @enablePreview
|
|
* @modules jdk.compiler/com.sun.tools.javac.api
|
|
* jdk.compiler/com.sun.tools.javac.main
|
|
* java.base/jdk.internal.classfile.impl
|
|
* @build toolbox.ToolBox InMemoryFileManager TestResult TestBase
|
|
* @run main EnclosingMethodTest
|
|
*/
|
|
|
|
import java.lang.classfile.*;
|
|
import java.lang.classfile.attribute.EnclosingMethodAttribute;
|
|
import jdk.internal.classfile.impl.BoundAttribute;
|
|
|
|
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);
|
|
ClassModel classFile = readClassFile(clazz);
|
|
checkEquals(countEnclosingMethodAttributes(classFile),
|
|
0L, "number of the EnclosingMethod attribute in the class is zero : "
|
|
+ classFile.thisClass().name());
|
|
} 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);
|
|
ClassModel classFile = readClassFile(clazz);
|
|
String className = clazz.getName();
|
|
checkEquals(countEnclosingMethodAttributes(classFile), 1l,
|
|
"number of the EnclosingMethod attribute in the class is one : "
|
|
+ clazz);
|
|
EnclosingMethodAttribute attr = classFile.findAttribute(Attributes.enclosingMethod()).orElse(null);
|
|
|
|
if (!checkNotNull(attr, "the EnclosingMethod attribute is not null : " + className)) {
|
|
// stop checking, attr is null. test case failed
|
|
return;
|
|
}
|
|
checkEquals(attr.attributeName().stringValue(),
|
|
"EnclosingMethod",
|
|
"attribute_name_index of EnclosingMethod attribute in the class : " + className);
|
|
checkEquals(((BoundAttribute<?>)attr).payloadLen(), 4,
|
|
"attribute_length of EnclosingMethod attribute in the class : " + className);
|
|
String expectedClassName = enclosingMethod.enclosingClazz().getName();
|
|
checkEquals(attr.enclosingClass().name().stringValue(),
|
|
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.enclosingMethod().isPresent()? attr.enclosingMethod().get().index(): 0, 0, String.format(
|
|
"enclosing method of EnclosingMethod attribute in the class %s is null", className));
|
|
} else {
|
|
String methodName = attr.enclosingMethodName().get().stringValue() + attr.enclosingMethodType().get().stringValue();
|
|
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(ClassModel classFile) {
|
|
return classFile.attributes().stream()
|
|
.filter(x -> x instanceof EnclosingMethodAttribute)
|
|
.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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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() {
|
|
};
|
|
}
|
|
}
|