1 /*
   2  * Copyright (c) 2013, 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 8010737
  27  * @summary javac, known parameter's names should be copied to automatically
  28  * generated constructors for inner classes
  29  * @modules jdk.jdeps/com.sun.tools.classfile
  30  *          jdk.compiler/com.sun.tools.javac.api
  31  *          jdk.compiler/com.sun.tools.javac.code
  32  *          jdk.compiler/com.sun.tools.javac.tree
  33  *          jdk.compiler/com.sun.tools.javac.util
  34  * @run main ParameterNamesAreNotCopiedToAnonymousInitTest check_class_file check_init_symbol
  35  */
  36 
  37 import java.io.File;
  38 import java.io.IOException;
  39 import java.lang.annotation.ElementType;
  40 import java.lang.annotation.Target;
  41 import java.nio.file.Paths;
  42 import java.util.Arrays;
  43 
  44 import javax.tools.JavaCompiler;
  45 import javax.tools.JavaFileObject;
  46 import javax.tools.StandardJavaFileManager;
  47 import javax.tools.ToolProvider;
  48 
  49 import com.sun.source.tree.CompilationUnitTree;
  50 import com.sun.source.tree.Tree;
  51 import com.sun.source.util.JavacTask;
  52 import com.sun.source.util.TaskEvent;
  53 import com.sun.source.util.TaskListener;
  54 import com.sun.tools.classfile.ClassFile;
  55 import com.sun.tools.classfile.Method;
  56 import com.sun.tools.javac.api.BasicJavacTask;
  57 import com.sun.tools.javac.code.Attribute.Compound;
  58 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  59 import com.sun.tools.javac.code.Symbol.VarSymbol;
  60 import com.sun.tools.javac.tree.JCTree;
  61 import com.sun.tools.javac.tree.TreeScanner;
  62 import com.sun.tools.javac.util.Assert;
  63 import com.sun.tools.javac.util.Context;
  64 import com.sun.tools.javac.util.List;
  65 import com.sun.tools.javac.util.Names;
  66 
  67 public class ParameterNamesAreNotCopiedToAnonymousInitTest {
  68 
  69     static final String noParamsErrorMsg =
  70             "Test most be invoked with at least one parameter: check_class_file " +
  71             "and/or check_init_symbol";
  72     static final String wrongParamsErrorMsg =
  73             "Accepted arguments are: check_class_file and check_init_symbol";
  74     static final String paramNameNotCopiedAssertionMsg =
  75             "The param name hasn't been copied to the init method";
  76     static final String noAnnotationsForParameterMsg =
  77             "No annotations for seek parameter";
  78     static final String seekMethodNotFound =
  79             "The seek init method was not found or conditions were not met";
  80     static final String nonNullParamPositionsMsg =
  81             "Parameter positions shold not be null";
  82     static final String compilationFailed =
  83             "Compilation failed";
  84     static final String seekMethodNotFoundMsg =
  85         "The seek method was not found";
  86 
  87     static final String ParamAnnotationClassName =
  88             ParameterNamesAreNotCopiedToAnonymousInitTest.class.getSimpleName() + "." +
  89             ParamAnnotation.class.getSimpleName();
  90 
  91     public static void main(String[] args) throws Exception {
  92         if (args.length == 0) {
  93             throw new Error(noParamsErrorMsg);
  94         }
  95         new ParameterNamesAreNotCopiedToAnonymousInitTest().run(args);
  96     }
  97 
  98     void run(String[] args) throws Exception {
  99         for (String arg : args) {
 100             if (arg.equals("check_class_file")) {
 101                 checkClassFile(new File(Paths.get(System.getProperty("test.classes"),
 102                         this.getClass().getName() + "$initParams$1.class").toUri()), 1);
 103                 checkClassFile(new File(Paths.get(System.getProperty("test.classes"),
 104                         this.getClass().getName() + "$Generics$1.class").toUri()), 2);
 105             } else if (arg.equals("check_init_symbol")) {
 106                 checkInitSymbol("m1", Arrays.asList(0), Arrays.asList("i"));
 107                 checkInitSymbol("m2", Arrays.asList(0, 1), Arrays.asList("t1", "t2"));
 108             } else {
 109                 error(wrongParamsErrorMsg);
 110             }
 111         }
 112     }
 113 
 114     void checkClassFile(final File cfile, int numberOfParams) throws Exception {
 115         ClassFile classFile = ClassFile.read(cfile);
 116         boolean methodFound = false;
 117         for (Method method : classFile.methods) {
 118             if (method.getName(classFile.constant_pool).equals("<init>")) {
 119                 methodFound = true;
 120             }
 121         }
 122         Assert.check(methodFound, seekMethodNotFoundMsg);
 123     }
 124 
 125     /*  This method expect a non-null ordered list of integers, listing the
 126      *  position of the parameters to be checked on the init method. Position 0
 127      *  corresponds to the first parameter.
 128      *
 129      *  As we are looking for a constructor of an anonymous class, the
 130      *  classOwnerName parameter must be the name of the method where the
 131      *  anonymous class is declared.
 132      */
 133     void checkInitSymbol(
 134             final String classOwnerName,
 135             final java.util.List<Integer> paramsToCheck,
 136             final java.util.List<String> paramNames)
 137             throws IOException {
 138         Assert.checkNonNull(paramsToCheck, nonNullParamPositionsMsg);
 139         JavaCompiler c = ToolProvider.getSystemJavaCompiler();
 140         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 141             Iterable<? extends JavaFileObject> fos =
 142                     fm.getJavaFileObjectsFromFiles(
 143                     Arrays.asList(new File(System.getProperty("test.src"),
 144                     this.getClass().getName() + ".java")));
 145             java.util.List<String> options = Arrays.asList(
 146                 "--add-exports", "jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED",
 147                 "--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
 148                 "--add-exports", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
 149                 "--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
 150                 "--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
 151                 "-d", System.getProperty("user.dir")
 152             );
 153             JavacTask task = (JavacTask) c.getTask(null, fm, null, options, null, fos);
 154 
 155             BasicJavacTask impl = (BasicJavacTask)task;
 156             Context context = impl.getContext();
 157             final Names names = Names.instance(context);
 158 
 159             task.addTaskListener(new TaskListener() {
 160 
 161                 @Override
 162                 public void started(TaskEvent e) {}
 163 
 164                 @Override
 165                 public void finished(TaskEvent e) {
 166                     class TheTreeScanner extends TreeScanner {
 167                         boolean foundAndCorrect = false;
 168 
 169                         @Override
 170                         public void visitMethodDef(JCTree.JCMethodDecl tree) {
 171                             ClassSymbol clazz = (ClassSymbol)tree.sym.owner;
 172                             if (clazz.owner.name.toString().equals(classOwnerName) &&
 173                                 tree.sym.name == names.init) {
 174 
 175                                 int currentParamPos = 0;
 176                                 int paramArrayIndex = 0;
 177 
 178                                 List<VarSymbol> params = tree.sym.params;
 179                                 while (params.nonEmpty() && paramArrayIndex < paramsToCheck.size()) {
 180                                     VarSymbol param = params.head;
 181                                     if (currentParamPos == paramsToCheck.get(paramArrayIndex)) {
 182                                         if (!param.name.toString()
 183                                                 .equals(paramNames.get(paramArrayIndex))) {
 184                                             error(paramNameNotCopiedAssertionMsg);
 185                                         }
 186                                         paramArrayIndex++;
 187                                     }
 188                                     currentParamPos++;
 189                                     params = params.tail;
 190                                 }
 191                                 foundAndCorrect = paramArrayIndex >= paramsToCheck.size();
 192                             }
 193                             super.visitMethodDef(tree);
 194                         }
 195                     }
 196 
 197                     if (e.getKind() == TaskEvent.Kind.ANALYZE) {
 198                         CompilationUnitTree compUnitTree = e.getCompilationUnit();
 199                         boolean foundAndCorrect = false;
 200                         for (Tree tree : compUnitTree.getTypeDecls()) {
 201                             TheTreeScanner scanner = new TheTreeScanner();
 202                             scanner.scan((JCTree) tree);
 203                             foundAndCorrect = foundAndCorrect | scanner.foundAndCorrect;
 204                         }
 205                         if (!foundAndCorrect) {
 206                             error(seekMethodNotFound);
 207                         }
 208                     }
 209                 }
 210             });
 211 
 212             if (!task.call()) {
 213                 error(compilationFailed);
 214             }
 215         }
 216     }
 217 
 218     void error(String msg) {
 219         throw new AssertionError(msg);
 220     }
 221 
 222     @Target(value = {ElementType.PARAMETER})
 223     @interface ParamAnnotation {}
 224 
 225     /*  If more cases are added in the future, it should be taken into account
 226      *  that method checkInitSymbol locates the inner class looking for its
 227      *  container method, which in the cases below are m1 and m2. So new cases
 228      *  must have different names for container methods or method checkInitSymbol
 229      *  should be changed.
 230      */
 231     public class initParams {
 232         public initParams(@ParamAnnotation int i) {}
 233 
 234         public void m1() {
 235             new initParams(2) {};
 236         }
 237     }
 238 
 239     class Generics<T1> {
 240         T1 obj1;
 241         Object obj2;
 242         <T2> Generics(@ParamAnnotation T1 t1, @ParamAnnotation T2 t2) {
 243             obj1 = t1;
 244             obj2 = t2;
 245         }
 246 
 247         void m2() {
 248             Generics<Integer> a = new <String>Generics<Integer>(
 249                     new Integer(11), "foo") {};
 250         }
 251     }
 252 }