1 /* 2 * Copyright (c) 2010, 2011, 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 6199075 27 * 28 * @summary Unambiguous varargs method calls flagged as ambiguous 29 * @author mcimadamore 30 * 31 */ 32 33 import com.sun.source.util.JavacTask; 34 import com.sun.tools.classfile.Instruction; 35 import com.sun.tools.classfile.Attribute; 36 import com.sun.tools.classfile.ClassFile; 37 import com.sun.tools.classfile.Code_attribute; 38 import com.sun.tools.classfile.ConstantPool.*; 39 import com.sun.tools.classfile.Method; 40 import com.sun.tools.javac.api.JavacTool; 41 import com.sun.tools.javac.util.List; 42 43 import java.io.File; 44 import java.net.URI; 45 import java.util.Arrays; 46 import java.util.Locale; 47 import javax.tools.Diagnostic; 48 import javax.tools.JavaCompiler; 49 import javax.tools.JavaFileObject; 50 import javax.tools.SimpleJavaFileObject; 51 import javax.tools.StandardJavaFileManager; 52 import javax.tools.ToolProvider; 53 54 public class T6199075 { 55 56 int checkCount = 0; 57 int bytecodeCheckCount = 0; 58 59 enum TypeKind { 60 BYTE("byte", "(byte)1", "[B", 0), 61 CHAR("char", "'c'", "[C", 1), 62 SHORT("short", "(short)1", "[S", 2), 63 INT("int", "1", "[I", 3), 64 LONG("long", "1L", "[J", 4), 65 FLOAT("float", "1.0F", "[F", 5), 66 DOUBLE("double", "1.0D", "[D", 6), 67 BOOLEAN("boolean", "true", "[Z", -1); 68 69 String typeString; 70 String valueString; 71 String bytecodeString; 72 private int subtypeTag; 73 74 TypeKind(String typeString, String valueString, String bytecodeString, int subtypeTag) { 75 this.typeString = typeString; 76 this.valueString = valueString; 77 this.bytecodeString = bytecodeString; 78 this.subtypeTag = subtypeTag; 79 } 80 81 boolean isSubtypeOf(TypeKind that) { 82 switch (this) { 83 case BOOLEAN: 84 return that == BOOLEAN; 85 case BYTE: 86 case CHAR: 87 return this.subtypeTag == that.subtypeTag || 88 this.subtypeTag + 2 <= that.subtypeTag; 89 default: 90 return this.subtypeTag <= that.subtypeTag; 91 } 92 } 93 } 94 95 enum ArgumentsArity { 96 ZERO(0), 97 ONE(1), 98 TWO(2), 99 THREE(3); 100 101 int arity; 102 103 ArgumentsArity(int arity) { 104 this.arity = arity; 105 } 106 107 String asExpressionList(TypeKind type) { 108 StringBuilder buf = new StringBuilder(); 109 String sep = ""; 110 for (int i = 0; i < arity; i++) { 111 buf.append(sep); 112 buf.append(type.valueString); 113 sep = ","; 114 } 115 return buf.toString(); 116 } 117 } 118 119 static class VarargsMethod { 120 TypeKind varargsElement; 121 122 VarargsMethod(TypeKind varargsElement) { 123 this.varargsElement = varargsElement; 124 } 125 126 @Override 127 public String toString() { 128 return "void m("+ varargsElement.typeString+ "... args) {}"; 129 } 130 131 boolean isApplicable(TypeKind actual, ArgumentsArity argsArity) { 132 return argsArity == ArgumentsArity.ZERO || 133 actual.isSubtypeOf(varargsElement); 134 } 135 136 boolean isMoreSpecificThan(VarargsMethod that) { 137 return varargsElement.isSubtypeOf(that.varargsElement); 138 } 139 } 140 141 public static void main(String... args) throws Exception { 142 new T6199075().test(); 143 } 144 145 void test() throws Exception { 146 for (TypeKind formal1 : TypeKind.values()) { 147 VarargsMethod m1 = new VarargsMethod(formal1); 148 for (TypeKind formal2 : TypeKind.values()) { 149 VarargsMethod m2 = new VarargsMethod(formal2); 150 for (TypeKind actual : TypeKind.values()) { 151 for (ArgumentsArity argsArity : ArgumentsArity.values()) { 152 compileAndCheck(m1, m2, actual, argsArity); 153 } 154 } 155 } 156 } 157 158 System.out.println("Total checks made: " + checkCount); 159 System.out.println("Bytecode checks made: " + bytecodeCheckCount); 160 } 161 162 // Create a single file manager and reuse it for each compile to save time. 163 StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null); 164 165 void compileAndCheck(VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) throws Exception { 166 final JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); 167 JavaSource source = new JavaSource(m1, m2, actual, argsArity); 168 ErrorChecker ec = new ErrorChecker(); 169 JavacTask ct = (JavacTask)tool.getTask(null, fm, ec, 170 null, null, Arrays.asList(source)); 171 ct.generate(); 172 check(source, ec, m1, m2, actual, argsArity); 173 } 174 175 void check(JavaSource source, ErrorChecker ec, VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) { 176 checkCount++; 177 boolean resolutionError = false; 178 VarargsMethod selectedMethod = null; 179 180 boolean m1_applicable = m1.isApplicable(actual, argsArity); 181 boolean m2_applicable = m2.isApplicable(actual, argsArity); 182 183 if (!m1_applicable && !m2_applicable) { 184 resolutionError = true; 185 } else if (m1_applicable && m2_applicable) { 186 //most specific 187 boolean m1_moreSpecific = m1.isMoreSpecificThan(m2); 188 boolean m2_moreSpecific = m2.isMoreSpecificThan(m1); 189 resolutionError = m1_moreSpecific == m2_moreSpecific; 190 selectedMethod = m1_moreSpecific ? m1 : m2; 191 } else { 192 selectedMethod = m1_applicable ? 193 m1 : m2; 194 } 195 196 if (ec.errorFound != resolutionError) { 197 throw new Error("invalid diagnostics for source:\n" + 198 source.getCharContent(true) + 199 "\nExpected resolution error: " + resolutionError + 200 "\nFound error: " + ec.errorFound + 201 "\nCompiler diagnostics:\n" + ec.printDiags()); 202 } else if (!resolutionError) { 203 verifyBytecode(selectedMethod); 204 } 205 } 206 207 void verifyBytecode(VarargsMethod selected) { 208 bytecodeCheckCount++; 209 File compiledTest = new File("Test.class"); 210 try { 211 ClassFile cf = ClassFile.read(compiledTest); 212 Method testMethod = null; 213 for (Method m : cf.methods) { 214 if (m.getName(cf.constant_pool).equals("test")) { 215 testMethod = m; 216 break; 217 } 218 } 219 if (testMethod == null) { 220 throw new Error("Test method not found"); 221 } 222 Code_attribute ea = (Code_attribute)testMethod.attributes.get(Attribute.Code); 223 if (testMethod == null) { 224 throw new Error("Code attribute for test() method not found"); 225 } 226 227 for (Instruction i : ea.getInstructions()) { 228 if (i.getMnemonic().equals("invokevirtual")) { 229 int cp_entry = i.getUnsignedShort(1); 230 CONSTANT_Methodref_info methRef = 231 (CONSTANT_Methodref_info)cf.constant_pool.get(cp_entry); 232 String type = methRef.getNameAndTypeInfo().getType(); 233 if (!type.contains(selected.varargsElement.bytecodeString)) { 234 throw new Error("Unexpected type method call: " + type); 235 } 236 break; 237 } 238 } 239 } catch (Exception e) { 240 e.printStackTrace(); 241 throw new Error("error reading " + compiledTest +": " + e); 242 } 243 } 244 245 static class JavaSource extends SimpleJavaFileObject { 246 247 static final String source_template = "class Test {\n" + 248 " #V1\n" + 249 " #V2\n" + 250 " void test() { m(#E); }\n" + 251 "}"; 252 253 String source; 254 255 public JavaSource(VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) { 256 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 257 source = source_template.replaceAll("#V1", m1.toString()). 258 replaceAll("#V2", m2.toString()). 259 replaceAll("#E", argsArity.asExpressionList(actual)); 260 } 261 262 @Override 263 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 264 return source; 265 } 266 } 267 268 static class ErrorChecker implements javax.tools.DiagnosticListener<JavaFileObject> { 269 270 boolean errorFound; 271 List<String> errDiags = List.nil(); 272 273 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 274 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { 275 errDiags = errDiags.append(diagnostic.getMessage(Locale.getDefault())); 276 errorFound = true; 277 } 278 } 279 280 String printDiags() { 281 StringBuilder buf = new StringBuilder(); 282 for (String s : errDiags) { 283 buf.append(s); 284 buf.append("\n"); 285 } 286 return buf.toString(); 287 } 288 } 289 }