test/tools/javac/MethodParameters.java

Sat, 29 Dec 2012 17:33:17 -0800

author
jjg
date
Sat, 29 Dec 2012 17:33:17 -0800
changeset 1473
31780dd06ec7
child 1478
a9cb93cca229
permissions
-rw-r--r--

8004727: Add compiler support for parameter reflection
Reviewed-by: jjg
Contributed-by: eric.mccorkle@oracle.com

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

mercurial