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