blob: 2a30c51f20b988c473b9053698fef4b88e5da75a [file] [log] [blame]
/*
* Copyright (c) 2014, 2016, 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
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.jdeps/com.sun.tools.classfile
* @build toolbox.ToolBox InMemoryFileManager TestResult TestBase
* @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() {
};
}
}