aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjjg <none@none>2009-10-19 13:38:09 -0700
committerjjg <none@none>2009-10-19 13:38:09 -0700
commit4488a80926da741ccb8fa6f0d9030d131f58ee14 (patch)
tree42f956ef433c1c72640cfd31de97e4e183f9de5e
parent2b8e51819ee2b50e260bcedd1aaa47bb0874bd11 (diff)
6889255: javac MethodSymbol throws NPE if ClassReader does not read parameter names correctlyjdk7-b75
Reviewed-by: darcy
-rw-r--r--src/share/classes/com/sun/tools/javac/code/Symbol.java53
-rw-r--r--src/share/classes/com/sun/tools/javac/jvm/ClassReader.java129
-rw-r--r--test/tools/javac/6889255/T6889255.java485
3 files changed, 640 insertions, 27 deletions
diff --git a/src/share/classes/com/sun/tools/javac/code/Symbol.java b/src/share/classes/com/sun/tools/javac/code/Symbol.java
index ab1f995a..6f30c605 100644
--- a/src/share/classes/com/sun/tools/javac/code/Symbol.java
+++ b/src/share/classes/com/sun/tools/javac/code/Symbol.java
@@ -1212,25 +1212,58 @@ public abstract class Symbol implements Element {
public List<VarSymbol> params() {
owner.complete();
if (params == null) {
- List<Name> names = savedParameterNames;
+ // If ClassReader.saveParameterNames has been set true, then
+ // savedParameterNames will be set to a list of names that
+ // matches the types in type.getParameterTypes(). If any names
+ // were not found in the class file, those names in the list will
+ // be set to the empty name.
+ // If ClassReader.saveParameterNames has been set false, then
+ // savedParameterNames will be null.
+ List<Name> paramNames = savedParameterNames;
savedParameterNames = null;
- if (names == null) {
- names = List.nil();
- int i = 0;
- for (Type t : type.getParameterTypes())
- names = names.prepend(name.table.fromString("arg" + i++));
- names = names.reverse();
- }
+ // discard the provided names if the list of names is the wrong size.
+ if (paramNames == null || paramNames.size() != type.getParameterTypes().size())
+ paramNames = List.nil();
ListBuffer<VarSymbol> buf = new ListBuffer<VarSymbol>();
+ List<Name> remaining = paramNames;
+ // assert: remaining and paramNames are both empty or both
+ // have same cardinality as type.getParameterTypes()
+ int i = 0;
for (Type t : type.getParameterTypes()) {
- buf.append(new VarSymbol(PARAMETER, names.head, t, this));
- names = names.tail;
+ Name paramName;
+ if (remaining.isEmpty()) {
+ // no names for any parameters available
+ paramName = createArgName(i, paramNames);
+ } else {
+ paramName = remaining.head;
+ remaining = remaining.tail;
+ if (paramName.isEmpty()) {
+ // no name for this specific parameter
+ paramName = createArgName(i, paramNames);
+ }
+ }
+ buf.append(new VarSymbol(PARAMETER, paramName, t, this));
+ i++;
}
params = buf.toList();
}
return params;
}
+ // Create a name for the argument at position 'index' that is not in
+ // the exclude list. In normal use, either no names will have been
+ // provided, in which case the exclude list is empty, or all the names
+ // will have been provided, in which case this method will not be called.
+ private Name createArgName(int index, List<Name> exclude) {
+ String prefix = "arg";
+ while (true) {
+ Name argName = name.table.fromString(prefix + index);
+ if (!exclude.contains(argName))
+ return argName;
+ prefix += "$";
+ }
+ }
+
public Symbol asMemberOf(Type site, Types types) {
return new MethodSymbol(flags_field, name, types.memberType(site, this), owner);
}
diff --git a/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java b/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java
index c8446b5e..146c7100 100644
--- a/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java
+++ b/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java
@@ -29,6 +29,7 @@ import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.CharBuffer;
+import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@@ -191,6 +192,16 @@ public class ClassReader implements Completer {
*/
boolean debugJSR308;
+ /** A table to hold the constant pool indices for method parameter
+ * names, as given in LocalVariableTable attributes.
+ */
+ int[] parameterNameIndices;
+
+ /**
+ * Whether or not any parameter names have been found.
+ */
+ boolean haveParameterNameIndices;
+
/** Get the ClassReader instance for this invocation. */
public static ClassReader instance(Context context) {
ClassReader instance = context.get(classReaderKey);
@@ -922,32 +933,33 @@ public class ClassReader implements Completer {
void read(Symbol sym, int attrLen) {
int newbp = bp + attrLen;
if (saveParameterNames) {
- // pick up parameter names from the variable table
- List<Name> parameterNames = List.nil();
- int firstParam = ((sym.flags() & STATIC) == 0) ? 1 : 0;
- int endParam = firstParam + Code.width(sym.type.getParameterTypes());
+ // Pick up parameter names from the variable table.
+ // Parameter names are not explicitly identified as such,
+ // but all parameter name entries in the LocalVariableTable
+ // have a start_pc of 0. Therefore, we record the name
+ // indicies of all slots with a start_pc of zero in the
+ // parameterNameIndicies array.
+ // Note that this implicitly honors the JVMS spec that
+ // there may be more than one LocalVariableTable, and that
+ // there is no specified ordering for the entries.
int numEntries = nextChar();
- for (int i=0; i<numEntries; i++) {
+ for (int i = 0; i < numEntries; i++) {
int start_pc = nextChar();
int length = nextChar();
int nameIndex = nextChar();
int sigIndex = nextChar();
int register = nextChar();
- if (start_pc == 0 &&
- firstParam <= register &&
- register < endParam) {
- int index = firstParam;
- for (Type t : sym.type.getParameterTypes()) {
- if (index == register) {
- parameterNames = parameterNames.prepend(readName(nameIndex));
- break;
- }
- index += Code.width(t);
+ if (start_pc == 0) {
+ // ensure array large enough
+ if (register >= parameterNameIndices.length) {
+ int newSize = Math.max(register, parameterNameIndices.length + 8);
+ parameterNameIndices =
+ Arrays.copyOf(parameterNameIndices, newSize);
}
+ parameterNameIndices[register] = nameIndex;
+ haveParameterNameIndices = true;
}
}
- parameterNames = parameterNames.reverse();
- ((MethodSymbol)sym).savedParameterNames = parameterNames;
}
bp = newbp;
}
@@ -1839,6 +1851,8 @@ public class ClassReader implements Completer {
syms.methodClass);
}
MethodSymbol m = new MethodSymbol(flags, name, type, currentOwner);
+ if (saveParameterNames)
+ initParameterNames(m);
Symbol prevOwner = currentOwner;
currentOwner = m;
try {
@@ -1846,9 +1860,90 @@ public class ClassReader implements Completer {
} finally {
currentOwner = prevOwner;
}
+ if (saveParameterNames)
+ setParameterNames(m, type);
return m;
}
+ /**
+ * Init the parameter names array.
+ * Parameter names are currently inferred from the names in the
+ * LocalVariableTable attributes of a Code attribute.
+ * (Note: this means parameter names are currently not available for
+ * methods without a Code attribute.)
+ * This method initializes an array in which to store the name indexes
+ * of parameter names found in LocalVariableTable attributes. It is
+ * slightly supersized to allow for additional slots with a start_pc of 0.
+ */
+ void initParameterNames(MethodSymbol sym) {
+ // make allowance for synthetic parameters.
+ final int excessSlots = 4;
+ int expectedParameterSlots =
+ Code.width(sym.type.getParameterTypes()) + excessSlots;
+ if (parameterNameIndices == null
+ || parameterNameIndices.length < expectedParameterSlots) {
+ parameterNameIndices = new int[expectedParameterSlots];
+ } else
+ Arrays.fill(parameterNameIndices, 0);
+ haveParameterNameIndices = false;
+ }
+
+ /**
+ * Set the parameter names for a symbol from the name index in the
+ * parameterNameIndicies array. The type of the symbol may have changed
+ * while reading the method attributes (see the Signature attribute).
+ * This may be because of generic information or because anonymous
+ * synthetic parameters were added. The original type (as read from
+ * the method descriptor) is used to help guess the existence of
+ * anonymous synthetic parameters.
+ * On completion, sym.savedParameter names will either be null (if
+ * no parameter names were found in the class file) or will be set to a
+ * list of names, one per entry in sym.type.getParameterTypes, with
+ * any missing names represented by the empty name.
+ */
+ void setParameterNames(MethodSymbol sym, Type jvmType) {
+ // if no names were found in the class file, there's nothing more to do
+ if (!haveParameterNameIndices)
+ return;
+
+ int firstParam = ((sym.flags() & STATIC) == 0) ? 1 : 0;
+ // the code in readMethod may have skipped the first parameter when
+ // setting up the MethodType. If so, we make a corresponding allowance
+ // here for the position of the first parameter. Note that this
+ // assumes the skipped parameter has a width of 1 -- i.e. it is not
+ // a double width type (long or double.)
+ if (sym.name == names.init && currentOwner.hasOuterInstance()) {
+ // Sometimes anonymous classes don't have an outer
+ // instance, however, there is no reliable way to tell so
+ // we never strip this$n
+ if (!currentOwner.name.isEmpty())
+ firstParam += 1;
+ }
+
+ if (sym.type != jvmType) {
+ // reading the method attributes has caused the symbol's type to
+ // be changed. (i.e. the Signature attribute.) This may happen if
+ // there are hidden (synthetic) parameters in the descriptor, but
+ // not in the Signature. The position of these hidden parameters
+ // is unspecified; for now, assume they are at the beginning, and
+ // so skip over them. The primary case for this is two hidden
+ // parameters passed into Enum constructors.
+ int skip = Code.width(jvmType.getParameterTypes())
+ - Code.width(sym.type.getParameterTypes());
+ firstParam += skip;
+ }
+ List<Name> paramNames = List.nil();
+ int index = firstParam;
+ for (Type t: sym.type.getParameterTypes()) {
+ int nameIdx = (index < parameterNameIndices.length
+ ? parameterNameIndices[index] : 0);
+ Name name = nameIdx == 0 ? names.empty : readName(nameIdx);
+ paramNames = paramNames.prepend(name);
+ index += Code.width(t);
+ }
+ sym.savedParameterNames = paramNames.reverse();
+ }
+
/** Skip a field or method
*/
void skipMember() {
diff --git a/test/tools/javac/6889255/T6889255.java b/test/tools/javac/6889255/T6889255.java
new file mode 100644
index 00000000..6d47c536
--- /dev/null
+++ b/test/tools/javac/6889255/T6889255.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6889255
+ * @summary ClassReader does not read parameter names correctly
+ */
+
+import java.io.*;
+import java.util.*;
+import javax.tools.StandardLocation;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Kinds;
+import com.sun.tools.javac.code.Scope;
+import com.sun.tools.javac.code.Symbol.*;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.code.TypeTags;
+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;
+
+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));
+ 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 = cr.enterClass(names.table.fromString(classname));
+ sym.complete();
+
+ if ((sym.flags() & Flags.INTERFACE) != 0 && !testInterfaces)
+ continue;
+
+ for (Scope.Entry e = sym.members_field.elems; e != null; e = e.sibling) {
+ System.err.println("Checking member " + e.sym);
+ switch (e.sym.kind) {
+ case Kinds.TYP: {
+ String name = e.sym.flatName().toString();
+ if (!classes.contains(name)) {
+ classes.add(name);
+ work.add(name);
+ }
+ break;
+ }
+ case Kinds.MTH:
+ verify((MethodSymbol) e.sym, 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 xN
+ if ((v.owner.flags() & Flags.BRIDGE) != 0)
+ return "x" + (i - 1);
+
+ // The rest of this method assumes the local conventions in the test program
+ Type t = v.type;
+ String s;
+ if (t.tag == TypeTags.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;
+}