1 /*
2  * Copyright (c) 2014, 2019, 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 /*
25  * @test
26  * @bug 8042931 8215470
27  * @summary Checking EnclosingMethod attribute of anonymous/local class.
28  * @library /tools/lib /tools/javac/lib ../lib
29  * @modules jdk.compiler/com.sun.tools.javac.api
30  *          jdk.compiler/com.sun.tools.javac.main
31  *          jdk.jdeps/com.sun.tools.classfile
32  * @build toolbox.ToolBox InMemoryFileManager TestResult TestBase
33  * @run main EnclosingMethodTest
34  */
35 
36 import com.sun.tools.classfile.Attribute;
37 import com.sun.tools.classfile.ClassFile;
38 import com.sun.tools.classfile.EnclosingMethod_attribute;
39 
40 import java.io.File;
41 import java.io.FilenameFilter;
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.stream.Stream;
49 
50 /**
51  * The test checks the enclosing method attribute of anonymous/local classes.
52  * The top-level class contains the anonymous and local classes to be tested. The test examines
53  * each inner class and determine whether the class should have the EnclosingMethod attribute or not.
54  * Golden information about enclosing methods are held in annotation {@code ExpectedEnclosingMethod}.
55  *
56  * The test assumes that a class must have the EnclosingMethod attribute if the class is annotated or
57  * if its parent class is annotated in case of anonymous class. In addition, classes
58  * named {@code VariableInitializer} are introduced to test variable initializer cases. These classes
59  * must not have the enclosing method attribute, but its anonymous derived class must.
60  * After classification of classes, the test checks whether classes contain the correct enclosing
61  * method attribute in case of anonymous/local class, or checks whether classes do not contain
62  * the EnclosingMethod attribute, otherwise.
63  *
64  * Test cases:
65  *   top-level class as enclosing class:
66  *     1. anonymous and local classes in static initializer;
67  *     2. anonymous and local classes in instance initializer;
68  *     3. anonymous and local classes in lambda;
69  *     4. anonymous and local classes in constructor;
70  *     5. anonymous and local classes in method;
71  *     6. static and instance variable initializer.
72  *
73  *   inner class as enclosing class:
74  *     1. anonymous and local classes in static initializer;
75  *     2. anonymous and local classes in instance initializer;
76  *     3. anonymous and local classes in lambda;
77  *     4. anonymous and local classes in constructor;
78  *     5. anonymous and local classes in method;
79  *     6. static and instance variable initializer.
80  *
81  *   enum as enclosing class:
82  *     1. anonymous and local classes in static initializer;
83  *     2. anonymous and local classes in instance initializer;
84  *     3. anonymous and local classes in lambda;
85  *     4. anonymous and local classes in constructor;
86  *     5. anonymous and local classes in method;
87  *     6. static and instance variable initializer.
88  *
89  *   interface as enclosing class:
90  *     1. anonymous and local classes in lambda;
91  *     2. anonymous and local classes in static method;
92  *     3. anonymous and local classes in default method;
93  *     4. static variable initializer.
94  *
95  *   annotation as enclosing class:
96  *     1. anonymous and local classes in lambda;
97  *     2. static variable initializer.
98  */
99 public class EnclosingMethodTest extends TestResult {
100 
101     private final Map<Class<?>, ExpectedEnclosingMethod> class2EnclosingMethod = new HashMap<>();
102     private final Set<Class<?>> noEnclosingMethod = new HashSet<>();
103 
EnclosingMethodTest()104     public EnclosingMethodTest() throws ClassNotFoundException {
105         Class<EnclosingMethodTest> outerClass = EnclosingMethodTest.class;
106         String outerClassName = outerClass.getSimpleName();
107         File testClasses = getClassDir();
108         FilenameFilter filter = (dir, name) -> name.matches(outerClassName + ".*\\.class");
109 
110         for (File file : testClasses.listFiles(filter)) {
111             Class<?> clazz = Class.forName(file.getName().replace(".class", ""));
112             if (clazz.isAnonymousClass()) {
113                 // anonymous class cannot be annotated, information is in its parent class.
114                 ExpectedEnclosingMethod declaredAnnotation =
115                         clazz.getSuperclass().getDeclaredAnnotation(ExpectedEnclosingMethod.class);
116                 class2EnclosingMethod.put(clazz, declaredAnnotation);
117             } else {
118                 ExpectedEnclosingMethod enclosingMethod = clazz.getDeclaredAnnotation(ExpectedEnclosingMethod.class);
119                 // if class is annotated and it does not contain information for variable initializer cases,
120                 // then it must have the enclosing method attribute.
121                 if (enclosingMethod != null && !clazz.getSimpleName().contains("VariableInitializer")) {
122                     class2EnclosingMethod.put(clazz, enclosingMethod);
123                 } else {
124                     noEnclosingMethod.add(clazz);
125                 }
126             }
127         }
128     }
129 
test()130     public void test() throws TestFailedException {
131         try {
132             testEnclosingMethodAttribute();
133             testLackOfEnclosingMethodAttribute();
134         } finally {
135             checkStatus();
136         }
137     }
138 
testLackOfEnclosingMethodAttribute()139     private void testLackOfEnclosingMethodAttribute() {
140         for (Class<?> clazz : noEnclosingMethod) {
141             try {
142                 addTestCase("Class should not have EnclosingMethod attribute : " + clazz);
143                 ClassFile classFile = readClassFile(clazz);
144                 checkEquals(countEnclosingMethodAttributes(classFile),
145                         0l, "number of the EnclosingMethod attribute in the class is zero : "
146                                 + classFile.getName());
147             } catch (Exception e) {
148                 addFailure(e);
149             }
150         }
151     }
152 
testEnclosingMethodAttribute()153     private void testEnclosingMethodAttribute() {
154         class2EnclosingMethod.forEach((clazz, enclosingMethod) -> {
155             try {
156                 String info = enclosingMethod.info() + " "
157                         + (clazz.isAnonymousClass() ? "anonymous" : "local");
158                 addTestCase(info);
159                 printf("Testing test case : %s\n", info);
160                 ClassFile classFile = readClassFile(clazz);
161                 String className = clazz.getName();
162                 checkEquals(countEnclosingMethodAttributes(classFile), 1l,
163                         "number of the EnclosingMethod attribute in the class is one : "
164                                 + clazz);
165                 EnclosingMethod_attribute attr = (EnclosingMethod_attribute)
166                         classFile.getAttribute(Attribute.EnclosingMethod);
167 
168                 if (!checkNotNull(attr, "the EnclosingMethod attribute is not null : " + className)) {
169                     // stop checking, attr is null. test case failed
170                     return;
171                 }
172                 checkEquals(classFile.constant_pool.getUTF8Value(attr.attribute_name_index),
173                         "EnclosingMethod",
174                         "attribute_name_index of EnclosingMethod attribute in the class : " + className);
175                 checkEquals(attr.attribute_length, 4,
176                         "attribute_length of EnclosingMethod attribute in the class : " + className);
177                 String expectedClassName = enclosingMethod.enclosingClazz().getName();
178                 checkEquals(classFile.constant_pool.getClassInfo(attr.class_index).getName(),
179                         expectedClassName, String.format(
180                         "enclosing class of EnclosingMethod attribute in the class %s is %s",
181                                 className, expectedClassName));
182 
183                 String expectedMethodName = enclosingMethod.enclosingMethod();
184                 if (expectedMethodName.isEmpty()) {
185                     // class does not have an enclosing method
186                     checkEquals(attr.method_index, 0, String.format(
187                             "enclosing method of EnclosingMethod attribute in the class %s is null", className));
188                 } else {
189                     String methodName = classFile.constant_pool.getNameAndTypeInfo(attr.method_index).getName();
190                     checkTrue(methodName.startsWith(expectedMethodName), String.format(
191                             "enclosing method of EnclosingMethod attribute in the class %s" +
192                                     " is method name %s" +
193                                     ", actual method name is %s",
194                             className, expectedMethodName, methodName));
195                 }
196             } catch (Exception e) {
197                 addFailure(e);
198             }
199         });
200     }
201 
countEnclosingMethodAttributes(ClassFile classFile)202     private long countEnclosingMethodAttributes(ClassFile classFile) {
203         return Stream.of(classFile.attributes.attrs)
204                 .filter(x -> x instanceof EnclosingMethod_attribute)
205                 .count();
206     }
207 
208     @Retention(RetentionPolicy.RUNTIME)
209     public @interface ExpectedEnclosingMethod {
info()210         String info();
enclosingClazz()211         Class<?> enclosingClazz();
enclosingMethod()212         String enclosingMethod() default "";
213     }
214 
main(String[] args)215     public static void main(String[] args) throws ClassNotFoundException, TestFailedException {
216         new EnclosingMethodTest().test();
217     }
218 
219     // Test cases: enclosing class is a top-level class
220     static {
221         // anonymous and local classes in static initializer
222         @ExpectedEnclosingMethod(
223                 info = "EnclosingStaticInitialization in EnclosingMethodTest",
224                 enclosingClazz = EnclosingMethodTest.class
225         )
226         class EnclosingStaticInitialization {
227         }
EnclosingStaticInitialization()228         new EnclosingStaticInitialization() {
229         };
230     }
231 
232     {
233         // anonymous and local classes in instance initializer
234         @ExpectedEnclosingMethod(
235                 info = "EnclosingInitialization in EnclosingMethodTest",
236                 enclosingClazz = EnclosingMethodTest.class
237         )
238         class EnclosingInitialization {
239         }
EnclosingInitialization()240         new EnclosingInitialization() {
241         };
242     }
243 
244     Runnable lambda = () -> {
245         // anonymous and local classes in lambda
246         @ExpectedEnclosingMethod(
247                 info = "EnclosingLambda in EnclosingMethodTest",
248                 enclosingMethod = "<init>",
249                 enclosingClazz = EnclosingMethodTest.class
250         )
251         class EnclosingLambda {
252         }
253         new EnclosingLambda() {
254         };
255     };
256 
EnclosingMethodTest(int i)257     EnclosingMethodTest(int i) {
258         // anonymous and local classes in constructor
259         @ExpectedEnclosingMethod(
260                 info = "EnclosingConstructor in EnclosingMethodTest",
261                 enclosingMethod = "<init>",
262                 enclosingClazz = EnclosingMethodTest.class
263         )
264         class EnclosingConstructor {
265         }
266         new EnclosingConstructor() {
267         };
268     }
269 
method()270     void method() {
271         // anonymous and local classes in method
272         @ExpectedEnclosingMethod(
273                 info = "EnclosingMethod in EnclosingMethodTest",
274                 enclosingMethod = "method",
275                 enclosingClazz = EnclosingMethodTest.class
276         )
277         class EnclosingMethod {
278         }
279         new EnclosingMethod() {
280         };
281     }
282 
283     @ExpectedEnclosingMethod(
284             info = "VariableInitializer in EnclosingMethodTest",
285             enclosingClazz = EnclosingMethodTest.class
286     )
287     static class VariableInitializer {
288     }
289 
290     // static variable initializer
291     private static final VariableInitializer cvi = new VariableInitializer() {
292     };
293 
294     // instance variable initializer
295     private final VariableInitializer ivi = new VariableInitializer() {
296     };
297 
298     // Test cases: enclosing class is an inner class
299     public static class notEnclosing01 {
300         static {
301             // anonymous and local classes in static initializer
302             @ExpectedEnclosingMethod(
303                     info = "EnclosingStaticInitialization in notEnclosing01",
304                     enclosingClazz = notEnclosing01.class
305             )
306             class EnclosingStaticInitialization {
307             }
EnclosingStaticInitialization()308             new EnclosingStaticInitialization() {
309             };
310         }
311 
312         {
313             // anonymous and local classes in instance initializer
314             @ExpectedEnclosingMethod(
315                     info = "EnclosingInitialization in notEnclosing01",
316                     enclosingClazz = notEnclosing01.class
317             )
318             class EnclosingInitialization {
319             }
EnclosingInitialization()320             new EnclosingInitialization() {
321             };
322         }
323 
324         Runnable lambda = () -> {
325             // anonymous and local classes in lambda
326             @ExpectedEnclosingMethod(
327                     info = "EnclosingLambda in notEnclosing01",
328                     enclosingMethod = "<init>",
329                     enclosingClazz = notEnclosing01.class
330             )
331             class EnclosingLambda {
332             }
333             new EnclosingLambda() {
334             };
335         };
336 
notEnclosing01()337         notEnclosing01() {
338             // anonymous and local classes in constructor
339             @ExpectedEnclosingMethod(
340                     info = "EnclosingConstructor in notEnclosing01",
341                     enclosingMethod = "<init>",
342                     enclosingClazz = notEnclosing01.class
343             )
344             class EnclosingConstructor {
345             }
346             new EnclosingConstructor() {
347             };
348         }
349 
method()350         void method() {
351             // anonymous and local classes in method
352             @ExpectedEnclosingMethod(
353                     info = "EnclosingMethod in notEnclosing01",
354                     enclosingMethod = "method",
355                     enclosingClazz = notEnclosing01.class
356             )
357             class EnclosingMethod {
358             }
359             new EnclosingMethod() {
360             };
361         }
362 
363         @ExpectedEnclosingMethod(
364                 info = "VariableInitializer in notEnclosing01",
365                 enclosingClazz = notEnclosing01.class
366         )
367         static class VariableInitializer {
368         }
369 
370         // static variable initializer
371         private static final VariableInitializer cvi = new VariableInitializer() {
372         };
373 
374         // instance variable initializer
375         private final VariableInitializer ivi = new VariableInitializer() {
376         };
377     }
378 
379     // Test cases: enclosing class is an interface
380     public interface notEnclosing02 {
381         Runnable lambda = () -> {
382             // anonymous and local classes in lambda
383             @ExpectedEnclosingMethod(
384                     info = "EnclosingLambda in notEnclosing02",
385                     enclosingMethod = "<clinit>",
386                     enclosingClazz = notEnclosing02.class
387             )
388             class EnclosingLambda {
389             }
390             new EnclosingLambda() {
391             };
392         };
393 
staticMethod()394         static void staticMethod() {
395             // anonymous and local classes in static method
396             @ExpectedEnclosingMethod(
397                     info = "EnclosingMethod in notEnclosing02",
398                     enclosingMethod = "staticMethod",
399                     enclosingClazz = notEnclosing02.class
400             )
401             class EnclosingMethod {
402             }
403             new EnclosingMethod() {
404             };
405         }
406 
defaultMethod()407         default void defaultMethod() {
408             // anonymous and local classes in default method
409             @ExpectedEnclosingMethod(
410                     info = "EnclosingMethod in notEnclosing02",
411                     enclosingMethod = "defaultMethod",
412                     enclosingClazz = notEnclosing02.class
413             )
414             class EnclosingMethod {
415             }
416             new EnclosingMethod() {
417             };
418         }
419 
420         @ExpectedEnclosingMethod(
421                 info = "VariableInitializer in notEnclosing02",
422                 enclosingClazz = notEnclosing02.class
423         )
424         static class VariableInitializer {
425         }
426 
427         // static variable initializer
428         VariableInitializer cvi = new VariableInitializer() {
429         };
430     }
431 
432     // Test cases: enclosing class is an enum
433     public enum notEnclosing03 {;
434 
435         static {
436             // anonymous and local classes in static initializer
437             @ExpectedEnclosingMethod(
438                     info = "EnclosingStaticInitialization in notEnclosing03",
439                     enclosingClazz = notEnclosing03.class
440             )
441             class EnclosingStaticInitialization {
442             }
EnclosingStaticInitialization()443             new EnclosingStaticInitialization() {
444             };
445         }
446 
447         {
448             // anonymous and local classes in instance initializer
449             @ExpectedEnclosingMethod(
450                     info = "EnclosingInitialization in notEnclosing03",
451                     enclosingClazz = notEnclosing03.class
452             )
453             class EnclosingInitialization {
454             }
EnclosingInitialization()455             new EnclosingInitialization() {
456             };
457         }
458 
459         Runnable lambda = () -> {
460             // anonymous and local classes in lambda
461             @ExpectedEnclosingMethod(
462                     info = "EnclosingLambda in notEnclosing03",
463                     enclosingMethod = "<init>",
464                     enclosingClazz = notEnclosing03.class
465             )
466             class EnclosingLambda {
467             }
468             new EnclosingLambda() {
469             };
470         };
471 
notEnclosing03()472         notEnclosing03() {
473             // anonymous and local classes in constructor
474             @ExpectedEnclosingMethod(
475                     info = "EnclosingConstructor in notEnclosing03",
476                     enclosingMethod = "<init>",
477                     enclosingClazz = notEnclosing03.class
478             )
479             class EnclosingConstructor {
480             }
481             new EnclosingConstructor() {
482             };
483         }
484 
method()485         void method() {
486             // anonymous and local classes in method
487             @ExpectedEnclosingMethod(
488                     info = "EnclosingMethod in notEnclosing03",
489                     enclosingMethod = "method",
490                     enclosingClazz = notEnclosing03.class
491             )
492             class EnclosingMethod {
493             }
494             new EnclosingMethod() {
495             };
496         }
497 
498         @ExpectedEnclosingMethod(
499                 info = "VariableInitializer in notEnclosing03",
500                 enclosingClazz = notEnclosing03.class
501         )
502         static class VariableInitializer {
503         }
504 
505         // static variable initializer
506         private static final VariableInitializer cvi = new VariableInitializer() {
507         };
508 
509         // instance variable initializer
510         private final VariableInitializer ivi = new VariableInitializer() {
511         };
512     }
513 
514     // Test cases: enclosing class is an annotation
515     public @interface notEnclosing04 {
516         Runnable lambda = () -> {
517             // anonymous and local classes in lambda
518             @ExpectedEnclosingMethod(
519                     info = "EnclosingLambda in notEnclosing04",
520                     enclosingMethod = "<clinit>",
521                     enclosingClazz = notEnclosing04.class
522             )
523             class EnclosingLambda {
524             }
525             new EnclosingLambda() {
526             };
527         };
528 
529         @ExpectedEnclosingMethod(
530                 info = "VariableInitializer in notEnclosing04",
531                 enclosingClazz = notEnclosing04.class
532         )
533         static class VariableInitializer {
534         }
535 
536         // static variable initializer
537         VariableInitializer cvi = new VariableInitializer() {
538         };
539     }
540 }
541