1/*
2 * Copyright (c) 2014, 2016, 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
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
36import com.sun.tools.classfile.Attribute;
37import com.sun.tools.classfile.ClassFile;
38import com.sun.tools.classfile.EnclosingMethod_attribute;
39
40import java.io.File;
41import java.io.FilenameFilter;
42import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
44import java.util.HashMap;
45import java.util.HashSet;
46import java.util.Map;
47import java.util.Set;
48import 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 */
99public class EnclosingMethodTest extends TestResult {
100
101    private final Map<Class<?>, ExpectedEnclosingMethod> class2EnclosingMethod = new HashMap<>();
102    private final Set<Class<?>> noEnclosingMethod = new HashSet<>();
103
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
130    public void test() throws TestFailedException {
131        try {
132            testEnclosingMethodAttribute();
133            testLackOfEnclosingMethodAttribute();
134        } finally {
135            checkStatus();
136        }
137    }
138
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
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
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 {
210        String info();
211        Class<?> enclosingClazz();
212        String enclosingMethod() default "";
213    }
214
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        }
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        }
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 = "lambda",
249                enclosingClazz = EnclosingMethodTest.class
250        )
251        class EnclosingLambda {
252        }
253        new EnclosingLambda() {
254        };
255    };
256
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
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            }
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            }
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 = "lambda",
329                    enclosingClazz = notEnclosing01.class
330            )
331            class EnclosingLambda {
332            }
333            new EnclosingLambda() {
334            };
335        };
336
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
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 = "lambda",
386                    enclosingClazz = notEnclosing02.class
387            )
388            class EnclosingLambda {
389            }
390            new EnclosingLambda() {
391            };
392        };
393
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
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            }
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            }
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 = "lambda",
464                    enclosingClazz = notEnclosing03.class
465            )
466            class EnclosingLambda {
467            }
468            new EnclosingLambda() {
469            };
470        };
471
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
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 = "lambda",
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