T7003595.java revision 2942:08092deced3f
1/*
2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 7003595
27 * @summary IncompatibleClassChangeError with unreferenced local class with subclass
28 * @modules jdk.jdeps/com.sun.tools.classfile
29 *          jdk.compiler/com.sun.tools.javac.api
30 *          jdk.compiler/com.sun.tools.javac.file
31 */
32
33import com.sun.source.util.JavacTask;
34import com.sun.tools.classfile.Attribute;
35import com.sun.tools.classfile.ClassFile;
36import com.sun.tools.classfile.InnerClasses_attribute;
37import com.sun.tools.classfile.ConstantPool.*;
38import com.sun.tools.javac.api.JavacTool;
39
40import java.io.File;
41import java.net.URI;
42import java.util.Arrays;
43import java.util.ArrayList;
44import javax.tools.JavaCompiler;
45import javax.tools.JavaFileObject;
46import javax.tools.SimpleJavaFileObject;
47import javax.tools.StandardJavaFileManager;
48import javax.tools.ToolProvider;
49
50
51public class T7003595 {
52
53    /** global decls ***/
54
55    //statistics
56    static int checkCount = 0;
57
58    enum ClassKind {
59        NESTED("static class #N { #B }", "$", true),
60        INNER("class #N { #B }", "$", false),
61        LOCAL_REF("void test() { class #N { #B }; new #N(); }", "$1", false),
62        LOCAL_NOREF("void test() { class #N { #B }; }", "$1", false),
63        ANON("void test() { new Object() { #B }; }", "$1", false),
64        NONE("", "", false);
65
66        String memberInnerStr;
67        String sep;
68        boolean staticAllowed;
69
70        private ClassKind(String memberInnerStr, String sep, boolean staticAllowed) {
71            this.memberInnerStr = memberInnerStr;
72            this.sep = sep;
73            this.staticAllowed = staticAllowed;
74        }
75
76        String getSource(String className, String outerName, String nested) {
77            return memberInnerStr.replaceAll("#O", outerName).
78                    replaceAll("#N", className).replaceAll("#B", nested);
79        }
80
81        static String getClassfileName(String[] names, ClassKind[] outerKinds, int pos) {
82            System.out.println(" pos = " + pos + " kind = " + outerKinds[pos] + " sep = " + outerKinds[pos].sep);
83            String name = outerKinds[pos] != ANON ?
84                    names[pos] : "";
85            if (pos == 0) {
86                return "Test" + outerKinds[pos].sep + name;
87            } else {
88                String outerStr = getClassfileName(names, outerKinds, pos - 1);
89                return outerStr + outerKinds[pos].sep + name;
90            }
91        }
92
93        boolean isAllowed(ClassKind nestedKind) {
94            return nestedKind != NESTED ||
95                    staticAllowed;
96        }
97    }
98
99    enum LocalInnerClass {
100        LOCAL_REF("class L {}; new L();", "Test$1L"),
101        LOCAL_NOREF("class L {};", "Test$1L"),
102        ANON("new Object() {};", "Test$1"),
103        NONE("", "");
104
105        String localInnerStr;
106        String canonicalInnerStr;
107
108        private LocalInnerClass(String localInnerStr, String canonicalInnerStr) {
109            this.localInnerStr = localInnerStr;
110            this.canonicalInnerStr = canonicalInnerStr;
111        }
112    }
113
114    public static void main(String... args) throws Exception {
115        // Create a single file manager and reuse it for each compile to save time.
116        try (StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null)) {
117            for (ClassKind ck1 : ClassKind.values()) {
118                String cname1 = "C1";
119                for (ClassKind ck2 : ClassKind.values()) {
120                    if (!ck1.isAllowed(ck2)) continue;
121                    String cname2 = "C2";
122                    for (ClassKind ck3 : ClassKind.values()) {
123                        if (!ck2.isAllowed(ck3)) continue;
124                        String cname3 = "C3";
125                        new T7003595(fm, new ClassKind[] {ck1, ck2, ck3}, new String[] { cname1, cname2, cname3 }).compileAndCheck();
126                    }
127                }
128            }
129        }
130
131        System.out.println("Total checks made: " + checkCount);
132    }
133
134    /** instance decls **/
135
136    ClassKind[] cks;
137    String[] cnames;
138    StandardJavaFileManager fm;
139
140    T7003595(StandardJavaFileManager fm, ClassKind[] cks, String[] cnames) {
141        this.fm = fm;
142        this.cks = cks;
143        this.cnames = cnames;
144    }
145
146    void compileAndCheck() throws Exception {
147        final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
148        JavaSource source = new JavaSource();
149        JavacTask ct = (JavacTask)tool.getTask(null, fm, null,
150                null, null, Arrays.asList(source));
151        ct.call();
152        verifyBytecode(source);
153    }
154
155    void verifyBytecode(JavaSource source) {
156        for (int i = 0; i < 3 ; i ++) {
157            if (cks[i] == ClassKind.NONE) break;
158            checkCount++;
159            String filename = cks[i].getClassfileName(cnames, cks, i);
160            File compiledTest = new File(filename + ".class");
161            try {
162                ClassFile cf = ClassFile.read(compiledTest);
163                if (cf == null) {
164                    throw new Error("Classfile not found: " + filename);
165                }
166
167                InnerClasses_attribute innerClasses = (InnerClasses_attribute)cf.getAttribute(Attribute.InnerClasses);
168
169                ArrayList<String> foundInnerSig = new ArrayList<>();
170                if (innerClasses != null) {
171                    for (InnerClasses_attribute.Info info : innerClasses.classes) {
172                        String foundSig = info.getInnerClassInfo(cf.constant_pool).getName();
173                        foundInnerSig.add(foundSig);
174                    }
175                }
176
177                ArrayList<String> expectedInnerSig = new ArrayList<>();
178                //add inner class (if any)
179                if (i < 2 && cks[i + 1] != ClassKind.NONE) {
180                    expectedInnerSig.add(cks[i + 1].getClassfileName(cnames, cks, i + 1));
181                }
182                //add inner classes
183                for (int j = 0 ; j != i + 1 && j < 3; j++) {
184                    expectedInnerSig.add(cks[j].getClassfileName(cnames, cks, j));
185                }
186
187                if (expectedInnerSig.size() != foundInnerSig.size()) {
188                    throw new Error("InnerClasses attribute for " + cnames[i] + " has wrong size\n" +
189                                    "expected " + expectedInnerSig.size() + "\n" +
190                                    "found " + innerClasses.number_of_classes + "\n" +
191                                    source);
192                }
193
194                for (String foundSig : foundInnerSig) {
195                    if (!expectedInnerSig.contains(foundSig)) {
196                        throw new Error("InnerClasses attribute for " + cnames[i] + " has unexpected signature: " +
197                                foundSig + "\n" + source + "\n" + expectedInnerSig);
198                    }
199                }
200
201                for (String expectedSig : expectedInnerSig) {
202                    if (!foundInnerSig.contains(expectedSig)) {
203                        throw new Error("InnerClasses attribute for " + cnames[i] + " does not contain expected signature: " +
204                                    expectedSig + "\n" + source);
205                    }
206                }
207            } catch (Exception e) {
208                e.printStackTrace();
209                throw new Error("error reading " + compiledTest +": " + e);
210            }
211        }
212    }
213
214    class JavaSource extends SimpleJavaFileObject {
215
216        static final String source_template = "class Test { #C }";
217
218        String source;
219
220        public JavaSource() {
221            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
222            String c3 = cks[2].getSource(cnames[2], cnames[1], "");
223            String c2 = cks[1].getSource(cnames[1], cnames[0], c3);
224            String c1 = cks[0].getSource(cnames[0], "Test", c2);
225            source = source_template.replace("#C", c1);
226        }
227
228        @Override
229        public String toString() {
230            return source;
231        }
232
233        @Override
234        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
235            return source;
236        }
237    }
238}
239