aoqi@0: /* aoqi@0: * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: /* aoqi@0: * @test aoqi@0: * @bug 7003595 aoqi@0: * @summary IncompatibleClassChangeError with unreferenced local class with subclass aoqi@0: */ aoqi@0: aoqi@0: import com.sun.source.util.JavacTask; aoqi@0: import com.sun.tools.classfile.Attribute; aoqi@0: import com.sun.tools.classfile.ClassFile; aoqi@0: import com.sun.tools.classfile.InnerClasses_attribute; aoqi@0: import com.sun.tools.classfile.ConstantPool.*; aoqi@0: import com.sun.tools.javac.api.JavacTool; aoqi@0: aoqi@0: import java.io.File; aoqi@0: import java.net.URI; aoqi@0: import java.util.Arrays; aoqi@0: import java.util.ArrayList; aoqi@0: import javax.tools.JavaCompiler; aoqi@0: import javax.tools.JavaFileObject; aoqi@0: import javax.tools.SimpleJavaFileObject; aoqi@0: import javax.tools.StandardJavaFileManager; aoqi@0: import javax.tools.ToolProvider; aoqi@0: aoqi@0: aoqi@0: public class T7003595 { aoqi@0: aoqi@0: /** global decls ***/ aoqi@0: aoqi@0: // Create a single file manager and reuse it for each compile to save time. aoqi@0: static StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null); aoqi@0: aoqi@0: //statistics aoqi@0: static int checkCount = 0; aoqi@0: aoqi@0: enum ClassKind { aoqi@0: NESTED("static class #N { #B }", "$", true), aoqi@0: INNER("class #N { #B }", "$", false), aoqi@0: LOCAL_REF("void test() { class #N { #B }; new #N(); }", "$1", false), aoqi@0: LOCAL_NOREF("void test() { class #N { #B }; }", "$1", false), aoqi@0: ANON("void test() { new Object() { #B }; }", "$1", false), aoqi@0: NONE("", "", false); aoqi@0: aoqi@0: String memberInnerStr; aoqi@0: String sep; aoqi@0: boolean staticAllowed; aoqi@0: aoqi@0: private ClassKind(String memberInnerStr, String sep, boolean staticAllowed) { aoqi@0: this.memberInnerStr = memberInnerStr; aoqi@0: this.sep = sep; aoqi@0: this.staticAllowed = staticAllowed; aoqi@0: } aoqi@0: aoqi@0: String getSource(String className, String outerName, String nested) { aoqi@0: return memberInnerStr.replaceAll("#O", outerName). aoqi@0: replaceAll("#N", className).replaceAll("#B", nested); aoqi@0: } aoqi@0: aoqi@0: static String getClassfileName(String[] names, ClassKind[] outerKinds, int pos) { aoqi@0: System.out.println(" pos = " + pos + " kind = " + outerKinds[pos] + " sep = " + outerKinds[pos].sep); aoqi@0: String name = outerKinds[pos] != ANON ? aoqi@0: names[pos] : ""; aoqi@0: if (pos == 0) { aoqi@0: return "Test" + outerKinds[pos].sep + name; aoqi@0: } else { aoqi@0: String outerStr = getClassfileName(names, outerKinds, pos - 1); aoqi@0: return outerStr + outerKinds[pos].sep + name; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: boolean isAllowed(ClassKind nestedKind) { aoqi@0: return nestedKind != NESTED || aoqi@0: staticAllowed; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: enum LocalInnerClass { aoqi@0: LOCAL_REF("class L {}; new L();", "Test$1L"), aoqi@0: LOCAL_NOREF("class L {};", "Test$1L"), aoqi@0: ANON("new Object() {};", "Test$1"), aoqi@0: NONE("", ""); aoqi@0: aoqi@0: String localInnerStr; aoqi@0: String canonicalInnerStr; aoqi@0: aoqi@0: private LocalInnerClass(String localInnerStr, String canonicalInnerStr) { aoqi@0: this.localInnerStr = localInnerStr; aoqi@0: this.canonicalInnerStr = canonicalInnerStr; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public static void main(String... args) throws Exception { aoqi@0: for (ClassKind ck1 : ClassKind.values()) { aoqi@0: String cname1 = "C1"; aoqi@0: for (ClassKind ck2 : ClassKind.values()) { aoqi@0: if (!ck1.isAllowed(ck2)) continue; aoqi@0: String cname2 = "C2"; aoqi@0: for (ClassKind ck3 : ClassKind.values()) { aoqi@0: if (!ck2.isAllowed(ck3)) continue; aoqi@0: String cname3 = "C3"; aoqi@0: new T7003595(new ClassKind[] {ck1, ck2, ck3}, new String[] { cname1, cname2, cname3 }).compileAndCheck(); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: System.out.println("Total checks made: " + checkCount); aoqi@0: } aoqi@0: aoqi@0: /** instance decls **/ aoqi@0: aoqi@0: ClassKind[] cks; aoqi@0: String[] cnames; aoqi@0: aoqi@0: T7003595(ClassKind[] cks, String[] cnames) { aoqi@0: this.cks = cks; aoqi@0: this.cnames = cnames; aoqi@0: } aoqi@0: aoqi@0: void compileAndCheck() throws Exception { aoqi@0: final JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); aoqi@0: JavaSource source = new JavaSource(); aoqi@0: JavacTask ct = (JavacTask)tool.getTask(null, fm, null, aoqi@0: null, null, Arrays.asList(source)); aoqi@0: ct.call(); aoqi@0: verifyBytecode(source); aoqi@0: } aoqi@0: aoqi@0: void verifyBytecode(JavaSource source) { aoqi@0: for (int i = 0; i < 3 ; i ++) { aoqi@0: if (cks[i] == ClassKind.NONE) break; aoqi@0: checkCount++; aoqi@0: String filename = cks[i].getClassfileName(cnames, cks, i); aoqi@0: File compiledTest = new File(filename + ".class"); aoqi@0: try { aoqi@0: ClassFile cf = ClassFile.read(compiledTest); aoqi@0: if (cf == null) { aoqi@0: throw new Error("Classfile not found: " + filename); aoqi@0: } aoqi@0: aoqi@0: InnerClasses_attribute innerClasses = (InnerClasses_attribute)cf.getAttribute(Attribute.InnerClasses); aoqi@0: aoqi@0: ArrayList foundInnerSig = new ArrayList<>(); aoqi@0: if (innerClasses != null) { aoqi@0: for (InnerClasses_attribute.Info info : innerClasses.classes) { aoqi@0: String foundSig = info.getInnerClassInfo(cf.constant_pool).getName(); aoqi@0: foundInnerSig.add(foundSig); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: ArrayList expectedInnerSig = new ArrayList<>(); aoqi@0: //add inner class (if any) aoqi@0: if (i < 2 && cks[i + 1] != ClassKind.NONE) { aoqi@0: expectedInnerSig.add(cks[i + 1].getClassfileName(cnames, cks, i + 1)); aoqi@0: } aoqi@0: //add inner classes aoqi@0: for (int j = 0 ; j != i + 1 && j < 3; j++) { aoqi@0: expectedInnerSig.add(cks[j].getClassfileName(cnames, cks, j)); aoqi@0: } aoqi@0: aoqi@0: if (expectedInnerSig.size() != foundInnerSig.size()) { aoqi@0: throw new Error("InnerClasses attribute for " + cnames[i] + " has wrong size\n" + aoqi@0: "expected " + expectedInnerSig.size() + "\n" + aoqi@0: "found " + innerClasses.number_of_classes + "\n" + aoqi@0: source); aoqi@0: } aoqi@0: aoqi@0: for (String foundSig : foundInnerSig) { aoqi@0: if (!expectedInnerSig.contains(foundSig)) { aoqi@0: throw new Error("InnerClasses attribute for " + cnames[i] + " has unexpected signature: " + aoqi@0: foundSig + "\n" + source + "\n" + expectedInnerSig); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: for (String expectedSig : expectedInnerSig) { aoqi@0: if (!foundInnerSig.contains(expectedSig)) { aoqi@0: throw new Error("InnerClasses attribute for " + cnames[i] + " does not contain expected signature: " + aoqi@0: expectedSig + "\n" + source); aoqi@0: } aoqi@0: } aoqi@0: } catch (Exception e) { aoqi@0: e.printStackTrace(); aoqi@0: throw new Error("error reading " + compiledTest +": " + e); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: class JavaSource extends SimpleJavaFileObject { aoqi@0: aoqi@0: static final String source_template = "class Test { #C }"; aoqi@0: aoqi@0: String source; aoqi@0: aoqi@0: public JavaSource() { aoqi@0: super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); aoqi@0: String c3 = cks[2].getSource(cnames[2], cnames[1], ""); aoqi@0: String c2 = cks[1].getSource(cnames[1], cnames[0], c3); aoqi@0: String c1 = cks[0].getSource(cnames[0], "Test", c2); aoqi@0: source = source_template.replace("#C", c1); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public String toString() { aoqi@0: return source; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public CharSequence getCharContent(boolean ignoreEncodingErrors) { aoqi@0: return source; aoqi@0: } aoqi@0: } aoqi@0: }