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 33 import com.sun.source.util.JavacTask; 34 import com.sun.tools.classfile.Attribute; 35 import com.sun.tools.classfile.ClassFile; 36 import com.sun.tools.classfile.InnerClasses_attribute; 37 import com.sun.tools.classfile.ConstantPool.*; 38 import com.sun.tools.javac.api.JavacTool; 39 40 import java.io.File; 41 import java.net.URI; 42 import java.util.Arrays; 43 import java.util.ArrayList; 44 import javax.tools.JavaCompiler; 45 import javax.tools.JavaFileObject; 46 import javax.tools.SimpleJavaFileObject; 47 import javax.tools.StandardJavaFileManager; 48 import javax.tools.ToolProvider; 49 50 51 public 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 }