MethodParametersTest.java revision 3516:d5420d4ccbaa
16699Schegar/*
26699Schegar * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
36699Schegar * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
46699Schegar *
56699Schegar * This code is free software; you can redistribute it and/or modify it
66699Schegar * under the terms of the GNU General Public License version 2 only, as
76699Schegar * published by the Free Software Foundation.
86699Schegar *
96699Schegar * This code is distributed in the hope that it will be useful, but WITHOUT
106699Schegar * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
116699Schegar * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
126699Schegar * version 2 for more details (a copy is included in the LICENSE file that
136699Schegar * accompanied this code).
146699Schegar *
156699Schegar * You should have received a copy of the GNU General Public License version
166699Schegar * 2 along with this work; if not, write to the Free Software Foundation,
176699Schegar * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
186699Schegar *
196699Schegar * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
206699Schegar * or visit www.oracle.com if you need additional information or have any
216699Schegar * questions.
226699Schegar */
236699Schegar
246699Schegar/*
256699Schegar * @test
266699Schegar * @bug 8004727
276699Schegar * @summary javac should generate method parameters correctly.
2812057Smchung * @modules jdk.jdeps/com.sun.tools.classfile
296699Schegar *          jdk.compiler/com.sun.tools.javac.code
306699Schegar *          jdk.compiler/com.sun.tools.javac.comp
316699Schegar *          jdk.compiler/com.sun.tools.javac.file
326699Schegar *          jdk.compiler/com.sun.tools.javac.main
336699Schegar *          jdk.compiler/com.sun.tools.javac.model
346699Schegar *          jdk.compiler/com.sun.tools.javac.util
356699Schegar */
366699Schegar// key: opt.arg.parameters
376699Schegarimport com.sun.tools.classfile.*;
386699Schegarimport com.sun.tools.javac.code.Symtab;
396699Schegarimport com.sun.tools.javac.file.JavacFileManager;
406699Schegarimport com.sun.tools.javac.main.Main;
416699Schegarimport com.sun.tools.javac.util.Context;
426699Schegarimport com.sun.tools.javac.util.Name;
436699Schegarimport com.sun.tools.javac.util.Names;
446699Schegarimport java.io.*;
456699Schegarimport javax.lang.model.element.*;
466699Schegarimport java.util.*;
476699Schegar
486699Schegarpublic class MethodParametersTest {
496699Schegar
506699Schegar    static final String Foo_name = "Foo";
516699Schegar    static final String Foo_contents =
526699Schegar        "public class Foo {\n" +
536699Schegar        "  Foo() {}\n" +
546699Schegar        "  void foo0() {}\n" +
556699Schegar        "  void foo2(int j, int k) {}\n" +
566699Schegar        "}";
576699Schegar    static final String Bar_name = "Bar";
586699Schegar    static final String Bar_contents =
596699Schegar        "public class Bar {\n" +
606699Schegar        "  Bar(int i) {}" +
616699Schegar        "  Foo foo() { return new Foo(); }\n" +
626699Schegar        "}";
636699Schegar    static final String Baz_name = "Baz";
646699Schegar    static final String Baz_contents =
656699Schegar        "public class Baz {\n" +
666699Schegar        "  int baz;" +
676699Schegar        "  Baz(int i) {}" +
686699Schegar        "}";
696699Schegar    static final String Qux_name = "Qux";
706699Schegar    static final String Qux_contents =
716699Schegar        "public class Qux extends Baz {\n" +
726699Schegar        "  Qux(int i) { super(i); }" +
736699Schegar        "}";
746699Schegar    static final File classesdir = new File("methodparameters");
756699Schegar
766699Schegar    public static void main(String... args) throws Exception {
776699Schegar        new MethodParametersTest().run();
786699Schegar    }
796699Schegar
806699Schegar    void run() throws Exception {
816699Schegar        classesdir.mkdir();
826699Schegar        final File Foo_java =
836699Schegar            writeFile(classesdir, Foo_name + ".java", Foo_contents);
846699Schegar        final File Bar_java =
856699Schegar            writeFile(classesdir, Bar_name + ".java", Bar_contents);
866699Schegar        final File Baz_java =
876699Schegar            writeFile(classesdir, Baz_name + ".java", Baz_contents);
886699Schegar        System.err.println("Test compile with -parameter");
896699Schegar        compile("-parameters", "-d", classesdir.getPath(), Foo_java.getPath());
906699Schegar        // First test: make sure javac doesn't choke to death on
916699Schegar        // MethodParameter attributes
926699Schegar        System.err.println("Test compile with classfile containing MethodParameter attributes");
936699Schegar        compile("-parameters", "-d", classesdir.getPath(),
946699Schegar                "-cp", classesdir.getPath(), Bar_java.getPath());
956699Schegar        System.err.println("Examine class foo");
966699Schegar        checkFoo();
976699Schegar        checkBar();
986699Schegar        System.err.println("Test debug information conflict");
996699Schegar        compile("-g", "-parameters", "-d", classesdir.getPath(),
1006699Schegar                "-cp", classesdir.getPath(), Baz_java.getPath());
1016699Schegar        System.err.println("Introducing debug information conflict");
1026699Schegar        Baz_java.delete();
1036699Schegar        modifyBaz(false);
1046699Schegar        System.err.println("Checking language model");
1056699Schegar        inspectBaz();
1066699Schegar        System.err.println("Permuting attributes");
1076699Schegar        modifyBaz(true);
1086699Schegar        System.err.println("Checking language model");
1096699Schegar        inspectBaz();
1106699Schegar
1116699Schegar        if(0 != errors)
1126699Schegar            throw new Exception("MethodParameters test failed with " +
1136699Schegar                                errors + " errors");
1146699Schegar    }
1156699Schegar
1166699Schegar    void inspectBaz() throws Exception {
1176699Schegar        final File Qux_java =
1186699Schegar            writeFile(classesdir, Qux_name + ".java", Qux_contents);
1196699Schegar        final String[] args = { "-parameters", "-d",
1206699Schegar                                classesdir.getPath(),
1216699Schegar                                "-cp", classesdir.getPath(),
1226699Schegar                                Qux_java.getPath() };
1236699Schegar        final StringWriter sw = new StringWriter();
1246699Schegar        final PrintWriter pw = new PrintWriter(sw);
1256699Schegar
1266699Schegar        // We need to be able to crack open javac and look at its data
1276699Schegar        // structures.  We'll rig up a compiler instance, but keep its
1286699Schegar        // Context, thus allowing us to get at the ClassReader.
1296699Schegar        Context context = new Context();
1306699Schegar        Main comp =  new Main("javac", pw);
1316699Schegar        JavacFileManager.preRegister(context);
1326699Schegar
1336699Schegar        // Compile Qux, which uses Baz.
1346699Schegar        comp.compile(args, context);
1356699Schegar        pw.close();
1366699Schegar        final String out = sw.toString();
1376699Schegar        if (out.length() > 0)
1386699Schegar            System.err.println(out);
1396699Schegar
1406699Schegar        // Now get the class finder, construct a name for Baz, and load it.
1416699Schegar        com.sun.tools.javac.code.ClassFinder cf =
1426699Schegar            com.sun.tools.javac.code.ClassFinder.instance(context);
1436699Schegar        Name name = Names.instance(context).fromString(Baz_name);
1446699Schegar        Symtab syms = Symtab.instance(context);
1456699Schegar
1466699Schegar        // Now walk down the language model and check the name of the
1476699Schegar        // parameter.
1486699Schegar        final Element baz = cf.loadClass(syms.unnamedModule, name);
1496699Schegar        for (Element e : baz.getEnclosedElements()) {
1506699Schegar            if (e instanceof ExecutableElement) {
1516699Schegar                final ExecutableElement ee = (ExecutableElement) e;
1526699Schegar                final List<? extends VariableElement> params =
1536699Schegar                    ee.getParameters();
1546699Schegar                if (1 != params.size())
1556699Schegar                    throw new Exception("Classfile Baz badly formed: wrong number of methods");
1566699Schegar                final VariableElement param = params.get(0);
1576699Schegar                if (!param.getSimpleName().contentEquals("baz")) {
1586699Schegar                    errors++;
1596699Schegar                    System.err.println("javac did not correctly resolve the metadata conflict, parameter's name reads as " + param.getSimpleName());
1606699Schegar                } else
1616699Schegar                    System.err.println("javac did correctly resolve the metadata conflict");
1626699Schegar            }
1636699Schegar        }
1646699Schegar    }
1656699Schegar
1666699Schegar    void modifyBaz(boolean flip) throws Exception {
1676699Schegar        final File Baz_class = new File(classesdir, Baz_name + ".class");
1686699Schegar        final ClassFile baz = ClassFile.read(Baz_class);
1696699Schegar        final int ind = baz.constant_pool.getUTF8Index("baz");
1706699Schegar        MethodParameters_attribute mpattr = null;
1716699Schegar        int mpind = 0;
1726699Schegar        Code_attribute cattr = null;
1736699Schegar        int cind = 0;
1746699Schegar
1756699Schegar        // Find the indexes of the MethodParameters and the Code attributes
1766699Schegar        if (baz.methods.length != 1)
1776699Schegar            throw new Exception("Classfile Baz badly formed: wrong number of methods");
1786699Schegar        if (!baz.methods[0].getName(baz.constant_pool).equals("<init>"))
1796699Schegar            throw new Exception("Classfile Baz badly formed: method has name " +
1806699Schegar                                baz.methods[0].getName(baz.constant_pool));
1816699Schegar        for (int i = 0; i < baz.methods[0].attributes.attrs.length; i++) {
1826699Schegar            if (baz.methods[0].attributes.attrs[i] instanceof
1836699Schegar                MethodParameters_attribute) {
1846699Schegar                mpattr = (MethodParameters_attribute)
1856699Schegar                    baz.methods[0].attributes.attrs[i];
1866699Schegar                mpind = i;
1876699Schegar            } else if (baz.methods[0].attributes.attrs[i] instanceof
1886699Schegar                       Code_attribute) {
1896699Schegar                cattr = (Code_attribute) baz.methods[0].attributes.attrs[i];
1906699Schegar                cind = i;
1916699Schegar            }
1926699Schegar        }
1936699Schegar        if (null == mpattr)
1946699Schegar            throw new Exception("Classfile Baz badly formed: no method parameters info");
1956699Schegar        if (null == cattr)
1966699Schegar            throw new Exception("Classfile Baz badly formed: no local variable table");
1976699Schegar
1986699Schegar        int flags = mpattr.method_parameter_table[0].flags;
1996699Schegar
2006699Schegar        // Alter the MethodParameters attribute, changing the name of
2016699Schegar        // the parameter from i to baz.  This requires Black Magic...
2026699Schegar        //
2036699Schegar        // The (well-designed) classfile library (correctly) does not
2046699Schegar        // allow us to mess around with the attribute data structures,
2056699Schegar        // or arbitrarily generate new ones.
2066699Schegar        //
2076699Schegar        // Instead, we install a new subclass of Attribute that
2086699Schegar        // hijacks the Visitor pattern and outputs the sequence of
2096699Schegar        // bytes that we want.  This only works in this particular
2106699Schegar        // instance, because we know we'll only every see one kind of
2116699Schegar        // visitor.
2126699Schegar        //
2136699Schegar        // If anyone ever changes the makeup of the Baz class, or
2146699Schegar        // tries to install some kind of visitor that gets run prior
2156699Schegar        // to serialization, this will break.
2166699Schegar        baz.methods[0].attributes.attrs[mpind] =
2176699Schegar            new Attribute(mpattr.attribute_name_index,
2186699Schegar                          mpattr.attribute_length) {
2196699Schegar                public <R, D> R accept(Visitor<R, D> visitor, D data) {
2206699Schegar                    if (data instanceof ByteArrayOutputStream) {
2216699Schegar                        ByteArrayOutputStream out =
2226699Schegar                            (ByteArrayOutputStream) data;
2236699Schegar                        out.write(1);
2246699Schegar                        out.write((ind >> 8) & 0xff);
2256699Schegar                        out.write(ind & 0xff);
2266699Schegar                        out.write((flags >> 24) & 0xff);
2276699Schegar                        out.write((flags >> 16) & 0xff);
2286699Schegar                        out.write((flags >> 8) & 0xff);
2296699Schegar                        out.write(flags & 0xff);
2306699Schegar                    } else
2316699Schegar                        throw new RuntimeException("Output stream is of type " + data.getClass() + ", which is not handled by this test.  Update the test and it should work.");
2326699Schegar                    return null;
2336699Schegar                }
2346699Schegar            };
2356699Schegar
2366699Schegar        // Flip the code and method attributes.  This is for checking
2376699Schegar        // that order doesn't matter.
2386699Schegar        if (flip) {
2396699Schegar            baz.methods[0].attributes.attrs[mpind] = cattr;
2406699Schegar            baz.methods[0].attributes.attrs[cind] = mpattr;
2416699Schegar        }
2426699Schegar
2436699Schegar        new ClassWriter().write(baz, Baz_class);
2446699Schegar    }
2456699Schegar
2466699Schegar    // Run a bunch of structural tests on foo to make sure it looks right.
2476699Schegar    void checkFoo() throws Exception {
2486699Schegar        final File Foo_class = new File(classesdir, Foo_name + ".class");
2496699Schegar        final ClassFile foo = ClassFile.read(Foo_class);
2506699Schegar        for (int i = 0; i < foo.methods.length; i++) {
2516699Schegar            System.err.println("Examine method Foo." + foo.methods[i].getName(foo.constant_pool));
2526699Schegar            if (foo.methods[i].getName(foo.constant_pool).equals("foo2")) {
2536699Schegar                for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++)
2546699Schegar                    if (foo.methods[i].attributes.attrs[j] instanceof
2556699Schegar                        MethodParameters_attribute) {
2566699Schegar                        MethodParameters_attribute mp =
2576699Schegar                            (MethodParameters_attribute)
2586699Schegar                            foo.methods[i].attributes.attrs[j];
2596699Schegar                        System.err.println("Foo.foo2 should have 2 parameters: j and k");
2606699Schegar                        if (2 != mp.method_parameter_table_length)
2616699Schegar                            error("expected 2 method parameter entries in foo2, got " +
2626699Schegar                                  mp.method_parameter_table_length);
2636699Schegar                        else if (!foo.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index).equals("j"))
2646699Schegar                            error("expected first parameter to foo2 to be \"j\", got \"" +
2656699Schegar                                  foo.constant_pool.getUTF8Value(mp.method_parameter_table[0].name_index) +
2666699Schegar                                  "\" instead");
2676699Schegar                        else if  (!foo.constant_pool.getUTF8Value(mp.method_parameter_table[1].name_index).equals("k"))
2686699Schegar                            error("expected first parameter to foo2 to be \"k\", got \"" +
2696699Schegar                                  foo.constant_pool.getUTF8Value(mp.method_parameter_table[1].name_index) +
2706699Schegar                                  "\" instead");
2716699Schegar                    }
2726699Schegar            }
2736699Schegar            else if (foo.methods[i].getName(foo.constant_pool).equals("<init>")) {
2746699Schegar                for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++) {
2756699Schegar                    if (foo.methods[i].attributes.attrs[j] instanceof
2766699Schegar                        MethodParameters_attribute)
2776699Schegar                        error("Zero-argument constructor shouldn't have MethodParameters");
2786699Schegar                }
2796699Schegar            }
2806699Schegar            else if (foo.methods[i].getName(foo.constant_pool).equals("foo0")) {
2816699Schegar                for (int j = 0; j < foo.methods[i].attributes.attrs.length; j++)
2826699Schegar                    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