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