1 /*
   2  * Copyright (c) 2012, 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 8004727
  27  * @summary javac should generate method parameters correctly.
  28  * @modules jdk.compiler/com.sun.tools.classfile
  29  *          jdk.compiler/com.sun.tools.javac.code
  30  *          jdk.compiler/com.sun.tools.javac.file
  31  *          jdk.compiler/com.sun.tools.javac.main
  32  *          jdk.compiler/com.sun.tools.javac.util
  33  */
  34 // key: opt.arg.parameters
  35 import com.sun.tools.classfile.*;
  36 import com.sun.tools.javac.file.JavacFileManager;
  37 import com.sun.tools.javac.main.Main;
  38 import com.sun.tools.javac.util.Context;
  39 import com.sun.tools.javac.util.Name;
  40 import com.sun.tools.javac.util.Names;
  41 import java.io.*;
  42 import javax.lang.model.element.*;
  43 import java.util.*;
  44 
  45 public class MethodParametersTest {
  46 
  47     static final String Foo_name = "Foo";
  48     static final String Foo_contents =
  49         "public class Foo {\n" +
  50         "  Foo() {}\n" +
  51         "  void foo0() {}\n" +
  52         "  void foo2(int j, int k) {}\n" +
  53         "}";
  54     static final String Bar_name = "Bar";
  55     static final String Bar_contents =
  56         "public class Bar {\n" +
  57         "  Bar(int i) {}" +
  58         "  Foo foo() { return new Foo(); }\n" +
  59         "}";
  60     static final String Baz_name = "Baz";
  61     static final String Baz_contents =
  62         "public class Baz {\n" +
  63         "  int baz;" +
  64         "  Baz(int i) {}" +
  65         "}";
  66     static final String Qux_name = "Qux";
  67     static final String Qux_contents =
  68         "public class Qux extends Baz {\n" +
  69         "  Qux(int i) { super(i); }" +
  70         "}";
  71     static final File classesdir = new File("methodparameters");
  72 
  73     public static void main(String... args) throws Exception {
  74         new MethodParametersTest().run();
  75     }
  76 
  77     void run() throws Exception {
  78         classesdir.mkdir();
  79         final File Foo_java =
  80             writeFile(classesdir, Foo_name + ".java", Foo_contents);
  81         final File Bar_java =
  82             writeFile(classesdir, Bar_name + ".java", Bar_contents);
  83         final File Baz_java =
  84             writeFile(classesdir, Baz_name + ".java", Baz_contents);
  85         System.err.println("Test compile with -parameter");
  86         compile("-parameters", "-d", classesdir.getPath(), Foo_java.getPath());
  87         // First test: make sure javac doesn't choke to death on
  88         // MethodParameter attributes
  89         System.err.println("Test compile with classfile containing MethodParameter attributes");
  90         compile("-parameters", "-d", classesdir.getPath(),
  91                 "-cp", classesdir.getPath(), Bar_java.getPath());
  92         System.err.println("Examine class foo");
  93         checkFoo();
  94         checkBar();
  95         System.err.println("Test debug information conflict");
  96         compile("-g", "-parameters", "-d", classesdir.getPath(),
  97                 "-cp", classesdir.getPath(), Baz_java.getPath());
  98         System.err.println("Introducing debug information conflict");
  99         Baz_java.delete();
 100         modifyBaz(false);
 101         System.err.println("Checking language model");
 102         inspectBaz();
 103         System.err.println("Permuting attributes");
 104         modifyBaz(true);
 105         System.err.println("Checking language model");
 106         inspectBaz();
 107 
 108         if(0 != errors)
 109             throw new Exception("MethodParameters test failed with " +
 110                                 errors + " errors");
 111     }
 112 
 113     void inspectBaz() throws Exception {
 114         final File Qux_java =
 115             writeFile(classesdir, Qux_name + ".java", Qux_contents);
 116         final String[] args = { "-XDsave-parameter-names", "-d",
 117                                 classesdir.getPath(),
 118                                 "-cp", classesdir.getPath(),
 119                                 Qux_java.getPath() };
 120         final StringWriter sw = new StringWriter();
 121         final PrintWriter pw = new PrintWriter(sw);
 122 
 123         // We need to be able to crack open javac and look at its data
 124         // structures.  We'll rig up a compiler instance, but keep its
 125         // Context, thus allowing us to get at the ClassReader.
 126         Context context = new Context();
 127         Main comp =  new Main("javac", pw);
 128         JavacFileManager.preRegister(context);
 129 
 130         // Compile Qux, which uses Baz.
 131         comp.compile(args, context);
 132         pw.close();
 133         final String out = sw.toString();
 134         if (out.length() > 0)
 135             System.err.println(out);
 136 
 137         // Now get the class finder, construct a name for Baz, and load it.
 138         com.sun.tools.javac.code.ClassFinder cf =
 139             com.sun.tools.javac.code.ClassFinder.instance(context);
 140         Name name = Names.instance(context).fromString(Baz_name);
 141 
 142         // Now walk down the language model and check the name of the
 143         // parameter.
 144         final Element baz = cf.loadClass(name);
 145         for (Element e : baz.getEnclosedElements()) {
 146             if (e instanceof ExecutableElement) {
 147                 final ExecutableElement ee = (ExecutableElement) e;
 148                 final List<? extends VariableElement> params =
 149                     ee.getParameters();
 150                 if (1 != params.size())
 151                     throw new Exception("Classfile Baz badly formed: wrong number of methods");
 152                 final VariableElement param = params.get(0);
 153                 if (!param.getSimpleName().contentEquals("baz")) {
 154                     errors++;
 155                     System.err.println("javac did not correctly resolve the metadata conflict, parameter's name reads as " + param.getSimpleName());
 156                 } else
 157                     System.err.println("javac did correctly resolve the metadata conflict");
 158             }
 159         }
 160     }
 161 
 162     void modifyBaz(boolean flip) throws Exception {
 163         final File Baz_class = new File(classesdir, Baz_name + ".class");
 164         final ClassFile baz = ClassFile.read(Baz_class);
 165         final int ind = baz.constant_pool.getUTF8Index("baz");
 166         MethodParameters_attribute mpattr = null;
 167         int mpind = 0;
 168         Code_attribute cattr = null;
 169         int cind = 0;
 170 
 171         // Find the indexes of the MethodParameters and the Code attributes
 172         if (baz.methods.length != 1)
 173             throw new Exception("Classfile Baz badly formed: wrong number of methods");
 174         if (!baz.methods[0].getName(baz.constant_pool).equals("<init>"))
 175             throw new Exception("Classfile Baz badly formed: method has name " +
 176                                 baz.methods[0].getName(baz.constant_pool));
 177         for (int i = 0; i < baz.methods[0].attributes.attrs.length; i++) {
 178             if (baz.methods[0].attributes.attrs[i] instanceof
 179                 MethodParameters_attribute) {
 180                 mpattr = (MethodParameters_attribute)
 181                     baz.methods[0].attributes.attrs[i];
 182                 mpind = i;
 183             } else if (baz.methods[0].attributes.attrs[i] instanceof
 184                        Code_attribute) {
 185                 cattr = (Code_attribute) baz.methods[0].attributes.attrs[i];
 186                 cind = i;
 187             }
 188         }
 189         if (null == mpattr)
 190             throw new Exception("Classfile Baz badly formed: no method parameters info");
 191         if (null == cattr)
 192             throw new Exception("Classfile Baz badly formed: no local variable table");
 193 
 194         int flags = mpattr.method_parameter_table[0].flags;
 195 
 196         // Alter the MethodParameters attribute, changing the name of
 197         // the parameter from i to baz.  This requires Black Magic...
 198         //
 199         // The (well-designed) classfile library (correctly) does not
 200         // allow us to mess around with the attribute data structures,
 201         // or arbitrarily generate new ones.
 202         //
 203         // Instead, we install a new subclass of Attribute that
 204         // hijacks the Visitor pattern and outputs the sequence of
 205         // bytes that we want.  This only works in this particular
 206         // instance, because we know we'll only every see one kind of
 207         // visitor.
 208         //
 209         // If anyone ever changes the makeup of the Baz class, or
 210         // tries to install some kind of visitor that gets run prior
 211         // to serialization, this will break.
 212         baz.methods[0].attributes.attrs[mpind] =
 213             new Attribute(mpattr.attribute_name_index,
 214                           mpattr.attribute_length) {
 215                 public <R, D> R accept(Visitor<R, D> visitor, D data) {
 216                     if (data instanceof ByteArrayOutputStream) {
 217                         ByteArrayOutputStream out =
 218                             (ByteArrayOutputStream) data;
 219                         out.write(1);
 220                         out.write((ind >> 8) & 0xff);
 221                         out.write(ind & 0xff);
 222                         out.write((flags >> 24) & 0xff);
 223                         out.write((flags >> 16) & 0xff);
 224                         out.write((flags >> 8) & 0xff);
 225                         out.write(flags & 0xff);
 226                     } else
 227                         throw new RuntimeException("Output stream is of type " + data.getClass() + ", which is not handled by this test.  Update the test and it should work.");
 228                     return null;
 229                 }
 230             };
 231 
 232         // Flip the code and method attributes.  This is for checking
 233         // that order doesn't matter.
 234         if (flip) {
 235             baz.methods[0].attributes.attrs[mpind] = cattr;
 236             baz.methods[0].attributes.attrs[cind] = mpattr;
 237         }
 238 
 239         new ClassWriter().write(baz, Baz_class);
 240     }
 241 
 242     // Run a bunch of structural tests on foo to make sure it looks right.
 243     void checkFoo() throws Exception {
 244         final File Foo_class = new File(classesdir, Foo_name + ".class");
 245         final ClassFile foo = ClassFile.read(Foo_class);
 246         for (int i = 0; i < foo.methods.length; i++) {
 247             System.err.println("Examine method Foo." + foo.methods[i].getName(foo.constant_pool));
 248             if (foo.methods[i].getName(foo.constant_pool).equals("foo2")) {
 249                 for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++)
 250                     if (foo.methods[i].attributes.attrs[j] instanceof
 251                         MethodParameters_attribute) {
 252                         MethodParameters_attribute mp =
 253                             (MethodParameters_attribute)
 254                             foo.methods[i].attributes.attrs[j];
 255                         System.err.println("Foo.foo2 should have 2 parameters: j and k");
 256                         if (2 != mp.method_parameter_table_length)
 257                             error("expected 2 method parameter entries in foo2, got " +
 258                                   mp.method_parameter_table_length);
 259                         else if (!foo.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index).equals("j"))
 260                             error("expected first parameter to foo2 to be \"j\", got \"" +
 261                                   foo.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index) +
 262                                   "\" instead");
 263                         else if  (!foo.constant_pool.getUTF8Value(mp.method_parameter_table[1].name_index).equals("k"))
 264                             error("expected first parameter to foo2 to be \"k\", got \"" +
 265                                   foo.constant_pool.getUTF8Value(mp.method_parameter_table[1].name_index) +
 266                                   "\" instead");
 267                     }
 268             }
 269             else if (foo.methods[i].getName(foo.constant_pool).equals("<init>")) {
 270                 for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++) {
 271                     if (foo.methods[i].attributes.attrs[j] instanceof
 272                         MethodParameters_attribute)
 273                         error("Zero-argument constructor shouldn't have MethodParameters");
 274                 }
 275             }
 276             else if (foo.methods[i].getName(foo.constant_pool).equals("foo0")) {
 277                 for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++)
 278                     if (foo.methods[i].attributes.attrs[j] instanceof
 279                         MethodParameters_attribute)
 280                         error("Zero-argument method shouldn't have MethodParameters");
 281             }
 282             else
 283                 error("Unknown method " + foo.methods[i].getName(foo.constant_pool) + " showed up in class Foo");
 284         }
 285     }
 286 
 287     // Run a bunch of structural tests on Bar to make sure it looks right.
 288     void checkBar() throws Exception {
 289         final File Bar_class = new File(classesdir, Bar_name + ".class");
 290         final ClassFile bar = ClassFile.read(Bar_class);
 291         for (int i = 0; i < bar.methods.length; i++) {
 292             System.err.println("Examine method Bar." + bar.methods[i].getName(bar.constant_pool));
 293             if (bar.methods[i].getName(bar.constant_pool).equals("<init>")) {
 294                 for (int j = 0; j < bar.methods[i].attributes.attrs.length; j++)
 295                     if (bar.methods[i].attributes.attrs[j] instanceof
 296                         MethodParameters_attribute) {
 297                         MethodParameters_attribute mp =
 298                             (MethodParameters_attribute)
 299                             bar.methods[i].attributes.attrs[j];
 300                         System.err.println("Bar constructor should have 1 parameter: i");
 301                         if (1 != mp.method_parameter_table_length)
 302                             error("expected 1 method parameter entries in constructor, got " +
 303                                   mp.method_parameter_table_length);
 304                         else if (!bar.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index).equals("i"))
 305                             error("expected first parameter to foo2 to be \"i\", got \"" +
 306                                   bar.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index) +
 307                                   "\" instead");
 308                     }
 309             }
 310             else if (bar.methods[i].getName(bar.constant_pool).equals("foo")) {
 311                 for (int j = 0; j < bar.methods[i].attributes.attrs.length; j++) {
 312                     if (bar.methods[i].attributes.attrs[j] instanceof
 313                         MethodParameters_attribute)
 314                         error("Zero-argument constructor shouldn't have MethodParameters");
 315                 }
 316             }
 317         }
 318     }
 319 
 320     String compile(String... args) throws Exception {
 321         System.err.println("compile: " + Arrays.asList(args));
 322         StringWriter sw = new StringWriter();
 323         PrintWriter pw = new PrintWriter(sw);
 324         int rc = com.sun.tools.javac.Main.compile(args, pw);
 325         pw.close();
 326         String out = sw.toString();
 327         if (out.length() > 0)
 328             System.err.println(out);
 329         if (rc != 0)
 330             error("compilation failed, rc=" + rc);
 331         return out;
 332     }
 333 
 334     File writeFile(File dir, String path, String body) throws IOException {
 335         File f = new File(dir, path);
 336         f.getParentFile().mkdirs();
 337         FileWriter out = new FileWriter(f);
 338         out.write(body);
 339         out.close();
 340         return f;
 341     }
 342 
 343     void error(String msg) {
 344         System.err.println("Error: " + msg);
 345         errors++;
 346     }
 347 
 348     int errors;
 349 }