changeset 451:2485f5641ed0 jdk7-b75

6889255: javac MethodSymbol throws NPE if ClassReader does not read parameter names correctly Reviewed-by: darcy
author jjg
date Mon, 19 Oct 2009 13:38:09 -0700
parents 6ba399eff2cb
children c8083dc525b6 a491ad1bb624
files src/share/classes/com/sun/tools/javac/code/Symbol.java src/share/classes/com/sun/tools/javac/jvm/ClassReader.java test/tools/javac/6889255/T6889255.java
diffstat 3 files changed, 640 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/com/sun/tools/javac/code/Symbol.java	Fri Oct 16 12:56:50 2009 -0700
+++ b/src/share/classes/com/sun/tools/javac/code/Symbol.java	Mon Oct 19 13:38:09 2009 -0700
@@ -1212,25 +1212,58 @@
         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);
         }
--- a/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java	Fri Oct 16 12:56:50 2009 -0700
+++ b/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java	Mon Oct 19 13:38:09 2009 -0700
@@ -29,6 +29,7 @@
 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 @@
      */
     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 @@
                 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 @@
                                       syms.methodClass);
         }
         MethodSymbol m = new MethodSymbol(flags, name, type, currentOwner);
+        if (saveParameterNames)
+            initParameterNames(m);
         Symbol prevOwner = currentOwner;
         currentOwner = m;
         try {
@@ -1846,9 +1860,90 @@
         } 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() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/6889255/T6889255.java	Mon Oct 19 13:38:09 2009 -0700
@@ -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;
+}