Mon, 17 Oct 2011 12:54:33 +0100
7097436: Project Coin: duplicate varargs warnings on method annotated with @SafeVarargs
Summary: Duplicate aliasing check during subtyping leads to spurious varargs diagnostic
Reviewed-by: jjg
1 /*
2 * Copyright (c) 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 */
24 /*
25 * @test
26 * @bug 7042566
27 * @summary Unambiguous varargs method calls flagged as ambiguous
28 */
30 import com.sun.source.util.JavacTask;
31 import com.sun.tools.classfile.Instruction;
32 import com.sun.tools.classfile.Attribute;
33 import com.sun.tools.classfile.ClassFile;
34 import com.sun.tools.classfile.Code_attribute;
35 import com.sun.tools.classfile.ConstantPool.*;
36 import com.sun.tools.classfile.Method;
37 import com.sun.tools.javac.api.JavacTool;
38 import com.sun.tools.javac.util.List;
40 import java.io.File;
41 import java.net.URI;
42 import java.util.Arrays;
43 import java.util.Locale;
44 import javax.tools.Diagnostic;
45 import javax.tools.JavaCompiler;
46 import javax.tools.JavaFileObject;
47 import javax.tools.SimpleJavaFileObject;
48 import javax.tools.StandardJavaFileManager;
49 import javax.tools.ToolProvider;
51 public class T7042566 {
53 VarargsMethod m1;
54 VarargsMethod m2;
55 TypeConfiguration actuals;
57 T7042566(TypeConfiguration m1_conf, TypeConfiguration m2_conf, TypeConfiguration actuals) {
58 this.m1 = new VarargsMethod(m1_conf);
59 this.m2 = new VarargsMethod(m2_conf);
60 this.actuals = actuals;
61 }
63 void compileAndCheck() throws Exception {
64 final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
65 JavaSource source = new JavaSource();
66 ErrorChecker ec = new ErrorChecker();
67 JavacTask ct = (JavacTask)tool.getTask(null, fm, ec,
68 null, null, Arrays.asList(source));
69 ct.call();
70 check(source, ec);
71 }
73 void check(JavaSource source, ErrorChecker ec) {
74 checkCount++;
75 boolean resolutionError = false;
76 VarargsMethod selectedMethod = null;
78 boolean m1_applicable = m1.isApplicable(actuals);
79 boolean m2_applicable = m2.isApplicable(actuals);
81 if (!m1_applicable && !m2_applicable) {
82 resolutionError = true;
83 } else if (m1_applicable && m2_applicable) {
84 //most specific
85 boolean m1_moreSpecific = m1.isMoreSpecificThan(m2);
86 boolean m2_moreSpecific = m2.isMoreSpecificThan(m1);
88 resolutionError = m1_moreSpecific == m2_moreSpecific;
89 selectedMethod = m1_moreSpecific ? m1 : m2;
90 } else {
91 selectedMethod = m1_applicable ?
92 m1 : m2;
93 }
95 if (ec.errorFound != resolutionError) {
96 throw new Error("invalid diagnostics for source:\n" +
97 source.getCharContent(true) +
98 "\nExpected resolution error: " + resolutionError +
99 "\nFound error: " + ec.errorFound +
100 "\nCompiler diagnostics:\n" + ec.printDiags());
101 } else if (!resolutionError) {
102 verifyBytecode(selectedMethod, source);
103 }
104 }
106 void verifyBytecode(VarargsMethod selected, JavaSource source) {
107 bytecodeCheckCount++;
108 File compiledTest = new File("Test.class");
109 try {
110 ClassFile cf = ClassFile.read(compiledTest);
111 Method testMethod = null;
112 for (Method m : cf.methods) {
113 if (m.getName(cf.constant_pool).equals("test")) {
114 testMethod = m;
115 break;
116 }
117 }
118 if (testMethod == null) {
119 throw new Error("Test method not found");
120 }
121 Code_attribute ea = (Code_attribute)testMethod.attributes.get(Attribute.Code);
122 if (testMethod == null) {
123 throw new Error("Code attribute for test() method not found");
124 }
126 for (Instruction i : ea.getInstructions()) {
127 if (i.getMnemonic().equals("invokevirtual")) {
128 int cp_entry = i.getUnsignedShort(1);
129 CONSTANT_Methodref_info methRef =
130 (CONSTANT_Methodref_info)cf.constant_pool.get(cp_entry);
131 String type = methRef.getNameAndTypeInfo().getType();
132 String sig = selected.parameterTypes.bytecodeSigStr;
133 if (!type.contains(sig)) {
134 throw new Error("Unexpected type method call: " + type + "" +
135 "\nfound: " + sig +
136 "\n" + source.getCharContent(true));
137 }
138 break;
139 }
140 }
141 } catch (Exception e) {
142 e.printStackTrace();
143 throw new Error("error reading " + compiledTest +": " + e);
144 }
145 }
147 class JavaSource extends SimpleJavaFileObject {
149 static final String source_template = "class Test {\n" +
150 " #V1\n" +
151 " #V2\n" +
152 " void test() { m(#E); }\n" +
153 "}";
155 String source;
157 public JavaSource() {
158 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
159 source = source_template.replaceAll("#V1", m1.toString()).
160 replaceAll("#V2", m2.toString()).
161 replaceAll("#E", actuals.expressionListStr);
162 }
164 @Override
165 public CharSequence getCharContent(boolean ignoreEncodingErrors) {
166 return source;
167 }
168 }
170 /** global decls ***/
172 // Create a single file manager and reuse it for each compile to save time.
173 static StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null);
175 //statistics
176 static int checkCount = 0;
177 static int bytecodeCheckCount = 0;
179 public static void main(String... args) throws Exception {
180 for (TypeConfiguration tconf1 : TypeConfiguration.values()) {
181 for (TypeConfiguration tconf2 : TypeConfiguration.values()) {
182 for (TypeConfiguration tconf3 : TypeConfiguration.values()) {
183 new T7042566(tconf1, tconf2, tconf3).compileAndCheck();
184 }
185 }
186 }
188 System.out.println("Total checks made: " + checkCount);
189 System.out.println("Bytecode checks made: " + bytecodeCheckCount);
190 }
192 enum TypeKind {
193 OBJECT("Object", "(Object)null", "Ljava/lang/Object;"),
194 STRING("String", "(String)null", "Ljava/lang/String;");
196 String typeString;
197 String valueString;
198 String bytecodeString;
200 TypeKind(String typeString, String valueString, String bytecodeString) {
201 this.typeString = typeString;
202 this.valueString = valueString;
203 this.bytecodeString = bytecodeString;
204 }
206 boolean isSubtypeOf(TypeKind that) {
207 return that == OBJECT ||
208 (that == STRING && this == STRING);
209 }
210 }
212 enum TypeConfiguration {
213 A(TypeKind.OBJECT),
214 B(TypeKind.STRING),
215 AA(TypeKind.OBJECT, TypeKind.OBJECT),
216 AB(TypeKind.OBJECT, TypeKind.STRING),
217 BA(TypeKind.STRING, TypeKind.OBJECT),
218 BB(TypeKind.STRING, TypeKind.STRING),
219 AAA(TypeKind.OBJECT, TypeKind.OBJECT, TypeKind.OBJECT),
220 AAB(TypeKind.OBJECT, TypeKind.OBJECT, TypeKind.STRING),
221 ABA(TypeKind.OBJECT, TypeKind.STRING, TypeKind.OBJECT),
222 ABB(TypeKind.OBJECT, TypeKind.STRING, TypeKind.STRING),
223 BAA(TypeKind.STRING, TypeKind.OBJECT, TypeKind.OBJECT),
224 BAB(TypeKind.STRING, TypeKind.OBJECT, TypeKind.STRING),
225 BBA(TypeKind.STRING, TypeKind.STRING, TypeKind.OBJECT),
226 BBB(TypeKind.STRING, TypeKind.STRING, TypeKind.STRING);
228 List<TypeKind> typeKindList;
229 String expressionListStr;
230 String parameterListStr;
231 String bytecodeSigStr;
233 private TypeConfiguration(TypeKind... typeKindList) {
234 this.typeKindList = List.from(typeKindList);
235 expressionListStr = asExpressionList();
236 parameterListStr = asParameterList();
237 bytecodeSigStr = asBytecodeString();
238 }
240 private String asExpressionList() {
241 StringBuilder buf = new StringBuilder();
242 String sep = "";
243 for (TypeKind tk : typeKindList) {
244 buf.append(sep);
245 buf.append(tk.valueString);
246 sep = ",";
247 }
248 return buf.toString();
249 }
251 private String asParameterList() {
252 StringBuilder buf = new StringBuilder();
253 String sep = "";
254 int count = 0;
255 for (TypeKind arg : typeKindList) {
256 buf.append(sep);
257 buf.append(arg.typeString);
258 if (count == (typeKindList.size() - 1)) {
259 buf.append("...");
260 }
261 buf.append(" ");
262 buf.append("arg" + count++);
263 sep = ",";
264 }
265 return buf.toString();
266 }
268 private String asBytecodeString() {
269 StringBuilder buf = new StringBuilder();
270 int count = 0;
271 for (TypeKind arg : typeKindList) {
272 if (count == (typeKindList.size() - 1)) {
273 buf.append("[");
274 }
275 buf.append(arg.bytecodeString);
276 count++;
277 }
278 return buf.toString();
279 }
280 }
282 static class VarargsMethod {
283 TypeConfiguration parameterTypes;
285 public VarargsMethod(TypeConfiguration parameterTypes) {
286 this.parameterTypes = parameterTypes;
287 }
289 @Override
290 public String toString() {
291 return "void m( " + parameterTypes.parameterListStr + ") {}";
292 }
294 boolean isApplicable(TypeConfiguration that) {
295 List<TypeKind> actuals = that.typeKindList;
296 List<TypeKind> formals = parameterTypes.typeKindList;
297 if ((actuals.size() - formals.size()) < -1)
298 return false; //not enough args
299 for (TypeKind actual : actuals) {
300 if (!actual.isSubtypeOf(formals.head))
301 return false; //type mismatch
302 formals = formals.tail.isEmpty() ?
303 formals :
304 formals.tail;
305 }
306 return true;
307 }
309 boolean isMoreSpecificThan(VarargsMethod that) {
310 List<TypeKind> actuals = parameterTypes.typeKindList;
311 List<TypeKind> formals = that.parameterTypes.typeKindList;
312 int checks = 0;
313 int expectedCheck = Math.max(actuals.size(), formals.size());
314 while (checks < expectedCheck) {
315 if (!actuals.head.isSubtypeOf(formals.head))
316 return false; //type mismatch
317 formals = formals.tail.isEmpty() ?
318 formals :
319 formals.tail;
320 actuals = actuals.tail.isEmpty() ?
321 actuals :
322 actuals.tail;
323 checks++;
324 }
325 return true;
326 }
327 }
329 static class ErrorChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
331 boolean errorFound;
332 List<String> errDiags = List.nil();
334 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
335 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
336 errDiags = errDiags.append(diagnostic.getMessage(Locale.getDefault()));
337 errorFound = true;
338 }
339 }
341 String printDiags() {
342 StringBuilder buf = new StringBuilder();
343 for (String s : errDiags) {
344 buf.append(s);
345 buf.append("\n");
346 }
347 return buf.toString();
348 }
349 }
350 }