| /* |
| * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @bug 6889255 |
| * @summary ClassReader does not read parameter names correctly |
| * @modules jdk.compiler/com.sun.tools.javac.code |
| * jdk.compiler/com.sun.tools.javac.file |
| * jdk.compiler/com.sun.tools.javac.jvm |
| * jdk.compiler/com.sun.tools.javac.util |
| */ |
| |
| import java.io.*; |
| import java.util.*; |
| import javax.tools.StandardLocation; |
| import com.sun.tools.javac.code.Flags; |
| import com.sun.tools.javac.code.Symbol; |
| import com.sun.tools.javac.code.Symbol.*; |
| import com.sun.tools.javac.code.Symtab; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.code.Type.ClassType; |
| import com.sun.tools.javac.code.TypeTag; |
| import com.sun.tools.javac.file.JavacFileManager; |
| import com.sun.tools.javac.jvm.ClassReader; |
| import com.sun.tools.javac.util.Context; |
| import com.sun.tools.javac.util.Names; |
| |
| import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; |
| |
| public class T6889255 { |
| boolean testInterfaces = true; |
| boolean testSyntheticMethods = true; |
| |
| // The following enums control the generation of the test methods to be compiled. |
| enum GenericKind { |
| NOT_GENERIC, |
| GENERIC |
| }; |
| |
| enum ClassKind { |
| CLASS("Clss"), |
| INTERFACE("Intf"), |
| ENUM("Enum"); |
| final String base; |
| ClassKind(String base) { this.base = base; } |
| }; |
| |
| enum NestedKind { |
| /** Declare methods inside the outermost container. */ |
| NONE, |
| /** Declare methods inside a container with a 'static' modifier. */ |
| NESTED, |
| /** Declare methods inside a container without a 'static' modifier. */ |
| INNER, |
| /** Declare methods inside a local class in an initializer. */ |
| INIT_LOCAL, |
| /** Declare methods inside an anonymous class in an initializer. */ |
| INIT_ANON, |
| /** Declare methods inside a local class in a method. */ |
| METHOD_LOCAL, |
| /** Declare methods inside an anonymous class in a method. */ |
| METHOD_ANON |
| }; |
| |
| enum MethodKind { |
| ABSTRACT, |
| CONSTRUCTOR, |
| METHOD, |
| STATIC_METHOD, |
| BRIDGE_METHOD |
| }; |
| |
| enum FinalKind { |
| /** Method body does not reference external final variables. */ |
| NO_FINAL, |
| /** Method body references external final variables. */ |
| USE_FINAL |
| }; |
| |
| public static void main(String... args) throws Exception { |
| new T6889255().run(); |
| } |
| |
| void run() throws Exception { |
| genTest(); |
| |
| test("no-args", false); |
| test("g", true, "-g"); |
| |
| if (errors > 0) |
| throw new Exception(errors + " errors found"); |
| } |
| |
| /** |
| * Create a file containing lots of method definitions to be tested. |
| * There are 3 sets of nested loops that generate the methods. |
| * 1. The outermost set declares [generic] (class | interface | enum) |
| * 2. The middle set declares [(nested | inner | anon | local)] class |
| * 3. The innermost set declares |
| * [generic] (constructor|method|static-method|bridge-method) [using final variables in outer scope] |
| * Invalid combinations are filtered out. |
| */ |
| void genTest() throws Exception { |
| BufferedWriter out = new BufferedWriter(new FileWriter("Test.java")); |
| |
| // This interface is used to force bridge methods to be generated, by |
| // implementing its methods with subtypes of Object |
| out.write("interface Base {\n"); |
| out.write(" Object base_m1(int i1);\n"); |
| out.write(" Object base_m2(int i1);\n"); |
| out.write("}\n"); |
| |
| int outerNum = 0; |
| // Outermost set of loops, to generate a top level container |
| for (GenericKind outerGenericKind: GenericKind.values()) { |
| for (ClassKind outerClassKind: ClassKind.values()) { |
| if (outerGenericKind == GenericKind.GENERIC && outerClassKind == ClassKind.ENUM) |
| continue; |
| String outerClassName = outerClassKind.base + (outerNum++); |
| String outerTypeArg = outerClassKind.toString().charAt(0) + "T"; |
| if (outerClassKind == ClassKind.CLASS) |
| out.write("abstract "); |
| out.write(outerClassKind.toString().toLowerCase() + " " + outerClassName); |
| if (outerGenericKind == GenericKind.GENERIC) |
| out.write("<" + outerTypeArg + ">"); |
| if (outerClassKind == ClassKind.INTERFACE) |
| out.write(" extends Base"); |
| else |
| out.write(" implements Base"); |
| out.write(" {\n"); |
| if (outerClassKind == ClassKind.ENUM) { |
| out.write(" E1(0,0,0), E2(0,0,0), E3(0,0,0);\n"); |
| out.write(" " + outerClassName + "(int i1, int i2, int i3) { }\n"); |
| } |
| // Middle set of loops, to generate an optional nested container |
| int nestedNum = 0; |
| int methodNum = 0; |
| for (GenericKind nestedGenericKind: GenericKind.values()) { |
| nextNestedKind: |
| for (NestedKind nestedKind: NestedKind.values()) { |
| // if the nested kind is none, there is no point iterating over all |
| // nested generic kinds, so arbitarily limit it to just one kind |
| if (nestedKind == NestedKind.NONE && nestedGenericKind != GenericKind.NOT_GENERIC) |
| continue; |
| if ((nestedKind == NestedKind.METHOD_ANON || nestedKind == NestedKind.INIT_ANON) |
| && nestedGenericKind == GenericKind.GENERIC) |
| continue; |
| String indent = " "; |
| boolean haveFinal = false; |
| switch (nestedKind) { |
| case METHOD_ANON: case METHOD_LOCAL: |
| if (outerClassKind == ClassKind.INTERFACE) |
| continue nextNestedKind; |
| out.write(indent + "void m" + + (nestedNum++) + "() {\n"); |
| indent += " "; |
| out.write(indent + "final int fi1 = 0;\n"); |
| haveFinal = true; |
| break; |
| case INIT_ANON: case INIT_LOCAL: |
| if (outerClassKind == ClassKind.INTERFACE) |
| continue nextNestedKind; |
| out.write(indent + "{\n"); |
| indent += " "; |
| break; |
| } |
| for (ClassKind nestedClassKind: ClassKind.values()) { |
| if ((nestedGenericKind == GenericKind.GENERIC) |
| && (nestedClassKind == ClassKind.ENUM)) |
| continue; |
| if ((nestedKind == NestedKind.METHOD_ANON || nestedKind == NestedKind.METHOD_LOCAL |
| || nestedKind == NestedKind.INIT_ANON || nestedKind == NestedKind.INIT_LOCAL) |
| && nestedClassKind != ClassKind.CLASS) |
| continue; |
| // if the nested kind is none, there is no point iterating over all |
| // nested class kinds, so arbitarily limit it to just one kind |
| if (nestedKind == NestedKind.NONE && nestedClassKind != ClassKind.CLASS) |
| continue; |
| |
| ClassKind methodClassKind; |
| String methodClassName; |
| boolean allowAbstractMethods; |
| boolean allowStaticMethods; |
| switch (nestedKind) { |
| case NONE: |
| methodClassKind = outerClassKind; |
| methodClassName = outerClassName; |
| allowAbstractMethods = (outerClassKind == ClassKind.CLASS); |
| allowStaticMethods = (outerClassKind != ClassKind.INTERFACE); |
| break; |
| case METHOD_ANON: |
| case INIT_ANON: |
| out.write(indent + "new Base() {\n"); |
| indent += " "; |
| methodClassKind = ClassKind.CLASS; |
| methodClassName = null; |
| allowAbstractMethods = false; |
| allowStaticMethods = false; |
| break; |
| default: { // INNER, NESTED, LOCAL |
| String nestedClassName = "N" + nestedClassKind.base + (nestedNum++); |
| String nestedTypeArg = nestedClassKind.toString().charAt(0) + "T"; |
| out.write(indent); |
| if (nestedKind == NestedKind.NESTED) |
| out.write("static "); |
| if (nestedClassKind == ClassKind.CLASS) |
| out.write("abstract "); |
| out.write(nestedClassKind.toString().toLowerCase() + " " + nestedClassName); |
| if (nestedGenericKind == GenericKind.GENERIC) |
| out.write("<" + nestedTypeArg + ">"); |
| if (nestedClassKind == ClassKind.INTERFACE) |
| out.write(" extends Base "); |
| else |
| out.write(" implements Base "); |
| out.write(" {\n"); |
| indent += " "; |
| if (nestedClassKind == ClassKind.ENUM) { |
| out.write(indent + "E1(0,0,0), E2(0,0,0), E3(0,0,0);\n"); |
| out.write(indent + nestedClassName + "(int i1, int i2, int i3) { }\n"); |
| } |
| methodClassKind = nestedClassKind; |
| methodClassName = nestedClassName; |
| allowAbstractMethods = (nestedClassKind == ClassKind.CLASS); |
| allowStaticMethods = (nestedKind == NestedKind.NESTED && nestedClassKind != ClassKind.INTERFACE); |
| break; |
| } |
| } |
| |
| // Innermost loops, to generate methods |
| for (GenericKind methodGenericKind: GenericKind.values()) { |
| for (FinalKind finalKind: FinalKind.values()) { |
| for (MethodKind methodKind: MethodKind.values()) { |
| // out.write("// " + outerGenericKind |
| // + " " + outerClassKind |
| // + " " + nestedKind |
| // + " " + nestedGenericKind |
| // + " " + nestedClassKind |
| // + " " + methodGenericKind |
| // + " " + finalKind |
| // + " " + methodKind |
| // + "\n"); |
| switch (methodKind) { |
| case CONSTRUCTOR: |
| if (nestedKind == NestedKind.METHOD_ANON || nestedKind == NestedKind.INIT_ANON) |
| break; |
| if (methodClassKind != ClassKind.CLASS) |
| break; |
| if (finalKind == FinalKind.USE_FINAL && !haveFinal) |
| break; |
| out.write(indent); |
| if (methodGenericKind == GenericKind.GENERIC) { |
| out.write("<CT> " + methodClassName + "(CT c1, CT c2"); |
| } else { |
| out.write(methodClassName + "(boolean b1, char c2"); |
| } |
| if (finalKind == FinalKind.USE_FINAL) { |
| // add a dummy parameter to avoid duplicate declaration |
| out.write(", int i3) { int i = fi1; }\n"); |
| } else |
| out.write(") { }\n"); |
| break; |
| case ABSTRACT: |
| if (!allowAbstractMethods) |
| continue; |
| // fallthrough |
| case METHOD: |
| if (finalKind == FinalKind.USE_FINAL && !haveFinal) |
| break; |
| out.write(indent); |
| if (methodKind == MethodKind.ABSTRACT) |
| out.write("abstract "); |
| if (methodGenericKind == GenericKind.GENERIC) |
| out.write("<MT> "); |
| out.write("void m" + (methodNum++) + "(int i1, long l2, float f3)"); |
| if (methodKind == MethodKind.ABSTRACT || methodClassKind == ClassKind.INTERFACE) |
| out.write(";\n"); |
| else { |
| out.write(" {"); |
| if (finalKind == FinalKind.USE_FINAL) |
| out.write(" int i = fi1;"); |
| out.write(" }\n"); |
| } |
| break; |
| case BRIDGE_METHOD: |
| if (methodGenericKind == GenericKind.GENERIC) |
| break; |
| out.write(indent); |
| // methods Base.base_m1 and Base.base_m2 are declared for the |
| // benefit of bridge methods. They need to be implemented |
| // whether or not a final variable is used. |
| String methodName = (finalKind == FinalKind.NO_FINAL ? "base_m1" : "base_m2"); |
| out.write("public String " + methodName + "(int i1)"); |
| if (methodClassKind == ClassKind.INTERFACE) |
| out.write(";\n"); |
| else { |
| out.write(" {"); |
| if (finalKind == FinalKind.USE_FINAL && haveFinal) |
| out.write(" int i = fi1;"); |
| out.write(" return null; }\n"); |
| } |
| break; |
| case STATIC_METHOD: |
| if (!allowStaticMethods) |
| break; |
| if (finalKind == FinalKind.USE_FINAL && !haveFinal) |
| break; |
| out.write(indent + "static "); |
| if (methodGenericKind == GenericKind.GENERIC) |
| out.write("<MT> "); |
| out.write("void m" + (methodNum++) + "(int i1, long l2, float f3) {"); |
| if (finalKind == FinalKind.USE_FINAL) |
| out.write(" int i = fi1;"); |
| out.write(" }\n"); |
| break; |
| } |
| |
| } |
| } |
| } |
| if (nestedKind != NestedKind.NONE) { |
| indent = indent.substring(0, indent.length() - 4); |
| out.write(indent + "};\n"); |
| } |
| } |
| switch (nestedKind) { |
| case METHOD_ANON: case METHOD_LOCAL: |
| case INIT_ANON: case INIT_LOCAL: |
| indent = indent.substring(0, indent.length() - 4); |
| out.write(indent + "}\n\n"); |
| } |
| } |
| } |
| out.write("}\n\n"); |
| } |
| } |
| out.close(); |
| } |
| |
| |
| void test(String testName, boolean expectNames, String... opts) throws Exception { |
| System.err.println("Test " + testName |
| + ": expectNames:" + expectNames |
| + " javacOpts:" + Arrays.asList(opts)); |
| |
| File outDir = new File(testName); |
| outDir.mkdirs(); |
| compile(outDir, opts); |
| |
| Context ctx = new Context(); |
| JavacFileManager fm = new JavacFileManager(ctx, true, null); |
| fm.setLocation(StandardLocation.CLASS_PATH, Arrays.asList(outDir)); |
| Symtab syms = Symtab.instance(ctx); |
| ClassReader cr = ClassReader.instance(ctx); |
| cr.saveParameterNames = true; |
| Names names = Names.instance(ctx); |
| |
| Set<String> classes = getTopLevelClasses(outDir); |
| Deque<String> work = new LinkedList<String>(classes); |
| String classname; |
| while ((classname = work.poll()) != null) { |
| System.err.println("Checking class " + classname); |
| ClassSymbol sym = syms.enterClass(syms.noModule, names.table.fromString(classname)); |
| sym.complete(); |
| |
| if ((sym.flags() & Flags.INTERFACE) != 0 && !testInterfaces) |
| continue; |
| |
| for (Symbol s : sym.members_field.getSymbols(NON_RECURSIVE)) { |
| System.err.println("Checking member " + s); |
| switch (s.kind) { |
| case TYP: { |
| String name = s.flatName().toString(); |
| if (!classes.contains(name)) { |
| classes.add(name); |
| work.add(name); |
| } |
| break; |
| } |
| case MTH: |
| verify((MethodSymbol) s, expectNames); |
| break; |
| } |
| |
| } |
| } |
| } |
| |
| void verify(MethodSymbol m, boolean expectNames) { |
| if ((m.flags() & Flags.SYNTHETIC) != 0 && !testSyntheticMethods) |
| return; |
| |
| //System.err.println("verify: " + m.params()); |
| int i = 1; |
| for (VarSymbol v: m.params()) { |
| String expectName; |
| if (expectNames) |
| expectName = getExpectedName(v, i); |
| else |
| expectName = "arg" + (i - 1); |
| checkEqual(expectName, v.name.toString()); |
| i++; |
| } |
| } |
| |
| String getExpectedName(VarSymbol v, int i) { |
| // special cases: |
| // synthetic method |
| if (((v.owner.owner.flags() & Flags.ENUM) != 0) |
| && v.owner.name.toString().equals("valueOf")) |
| return "name"; |
| // interfaces don't have saved names |
| // -- no Code attribute for the LocalVariableTable attribute |
| if ((v.owner.owner.flags() & Flags.INTERFACE) != 0) |
| return "arg" + (i - 1); |
| // abstract methods don't have saved names |
| // -- no Code attribute for the LocalVariableTable attribute |
| if ((v.owner.flags() & Flags.ABSTRACT) != 0) |
| return "arg" + (i - 1); |
| // bridge methods use argN. No LVT for them anymore |
| if ((v.owner.flags() & Flags.BRIDGE) != 0) |
| return "arg" + (i - 1); |
| |
| // The rest of this method assumes the local conventions in the test program |
| Type t = v.type; |
| String s; |
| if (t.hasTag(TypeTag.CLASS)) |
| s = ((ClassType) t).tsym.name.toString(); |
| else |
| s = t.toString(); |
| return String.valueOf(Character.toLowerCase(s.charAt(0))) + i; |
| } |
| |
| void compile(File outDir, String... opts) throws Exception { |
| //File testSrc = new File(System.getProperty("test.src"), "."); |
| List<String> args = new ArrayList<String>(); |
| args.add("-d"); |
| args.add(outDir.getPath()); |
| args.addAll(Arrays.asList(opts)); |
| //args.add(new File(testSrc, "Test.java").getPath()); |
| args.add("Test.java"); |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw); |
| int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw); |
| pw.close(); |
| if (rc != 0) { |
| System.err.println(sw.toString()); |
| throw new Exception("compilation failed unexpectedly"); |
| } |
| } |
| |
| Set<String> getTopLevelClasses(File outDir) { |
| Set<String> classes = new HashSet<String>(); |
| for (String f: outDir.list()) { |
| if (f.endsWith(".class") && !f.contains("$")) |
| classes.add(f.replace(".class", "")); |
| } |
| return classes; |
| } |
| |
| void checkEqual(String expect, String found) { |
| if (!expect.equals(found)) |
| error("mismatch: expected:" + expect + " found:" + found); |
| } |
| |
| void error(String msg) { |
| System.err.println(msg); |
| errors++; |
| throw new Error(); |
| } |
| |
| int errors; |
| } |