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