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 }