# HG changeset patch # User mcimadamore # Date 1291994657 0 # Node ID e0a3aeefe4e7a8555a10be740fb64ec45dca3e26 # Parent 00834a375696d5ca6bdfda65582e000949a9fe75 6199075: Unambiguous varargs method calls flagged as ambiguous Summary: javac does not implement overload resolution w.r.t. varargs methods as described in the JLS Reviewed-by: jjg diff -r 00834a375696 -r e0a3aeefe4e7 langtools/src/share/classes/com/sun/tools/javac/code/Types.java --- a/langtools/src/share/classes/com/sun/tools/javac/code/Types.java Fri Dec 10 15:23:42 2010 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/code/Types.java Fri Dec 10 15:24:17 2010 +0000 @@ -1318,6 +1318,13 @@ } } + public Type elemtypeOrType(Type t) { + Type elemtype = elemtype(t); + return elemtype != null ? + elemtype : + t; + } + /** * Mapping to take element type of an arraytype */ diff -r 00834a375696 -r e0a3aeefe4e7 langtools/src/share/classes/com/sun/tools/javac/comp/Resolve.java --- a/langtools/src/share/classes/com/sun/tools/javac/comp/Resolve.java Fri Dec 10 15:23:42 2010 +0000 +++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Resolve.java Fri Dec 10 15:24:17 2010 +0000 @@ -470,7 +470,9 @@ throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args if (useVarargs) { - Type elt = types.elemtype(varargsFormal); + //note: if applicability check is triggered by most specific test, + //the last argument of a varargs is _not_ an array type (see JLS 15.12.2.5) + Type elt = types.elemtypeOrType(varargsFormal); while (argtypes.nonEmpty()) { if (!types.isConvertible(argtypes.head, elt, warn)) throw inapplicableMethodException.setMessage("varargs.argument.mismatch", @@ -827,24 +829,30 @@ List fromArgs = from.type.getParameterTypes(); List toArgs = to.type.getParameterTypes(); if (useVarargs && - toArgs.length() < fromArgs.length() && (from.flags() & VARARGS) != 0 && (to.flags() & VARARGS) != 0) { - //if we are checking a varargs method 'from' against another varargs - //method 'to' (where arity of 'to' < arity of 'from') then expand signature - //of 'to' to 'fit' arity of 'from' (this means adding fake formals to 'to' - //until 'to' signature has the same arity as 'from') - ListBuffer args = ListBuffer.lb(); Type varargsTypeFrom = fromArgs.last(); Type varargsTypeTo = toArgs.last(); - while (fromArgs.head != varargsTypeFrom) { - args.append(toArgs.head == varargsTypeTo ? types.elemtype(varargsTypeTo) : toArgs.head); - fromArgs = fromArgs.tail; - toArgs = toArgs.head == varargsTypeTo ? - toArgs : - toArgs.tail; + ListBuffer args = ListBuffer.lb(); + if (toArgs.length() < fromArgs.length()) { + //if we are checking a varargs method 'from' against another varargs + //method 'to' (where arity of 'to' < arity of 'from') then expand signature + //of 'to' to 'fit' arity of 'from' (this means adding fake formals to 'to' + //until 'to' signature has the same arity as 'from') + while (fromArgs.head != varargsTypeFrom) { + args.append(toArgs.head == varargsTypeTo ? types.elemtype(varargsTypeTo) : toArgs.head); + fromArgs = fromArgs.tail; + toArgs = toArgs.head == varargsTypeTo ? + toArgs : + toArgs.tail; + } + } else { + //formal argument list is same as original list where last + //argument (array type) is removed + args.appendList(toArgs.reverse().tail.reverse()); } - args.append(varargsTypeTo); + //append varargs element type as last synthetic formal + args.append(types.elemtype(varargsTypeTo)); MethodSymbol msym = new MethodSymbol(to.flags_field, to.name, (Type)to.type.clone(), //see: 6990136 diff -r 00834a375696 -r e0a3aeefe4e7 langtools/test/tools/javac/varargs/6199075/T6199075.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/varargs/6199075/T6199075.java Fri Dec 10 15:24:17 2010 +0000 @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6199075 + * + * @summary Unambiguous varargs method calls flagged as ambiguous + * @author mcimadamore + * + */ + +import com.sun.source.util.JavacTask; +import com.sun.tools.classfile.Instruction; +import com.sun.tools.classfile.Attribute; +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.Code_attribute; +import com.sun.tools.classfile.ConstantPool.*; +import com.sun.tools.classfile.Method; +import com.sun.tools.javac.util.List; + +import java.io.File; +import java.net.URI; +import java.util.Arrays; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +public class T6199075 { + + int checkCount = 0; + int bytecodeCheckCount = 0; + + enum TypeKind { + BYTE("byte", "(byte)1", "[B", 0), + CHAR("char", "'c'", "[C", 1), + SHORT("short", "(short)1", "[S", 2), + INT("int", "1", "[I", 3), + LONG("long", "1L", "[J", 4), + FLOAT("float", "1.0F", "[F", 5), + DOUBLE("double", "1.0D", "[D", 6), + BOOLEAN("boolean", "true", "[Z", -1); + + String typeString; + String valueString; + String bytecodeString; + private int subtypeTag; + + TypeKind(String typeString, String valueString, String bytecodeString, int subtypeTag) { + this.typeString = typeString; + this.valueString = valueString; + this.bytecodeString = bytecodeString; + this.subtypeTag = subtypeTag; + } + + boolean isSubtypeOf(TypeKind that) { + switch (this) { + case BOOLEAN: + return that == BOOLEAN; + case BYTE: + case CHAR: + return this.subtypeTag == that.subtypeTag || + this.subtypeTag + 2 <= that.subtypeTag; + default: + return this.subtypeTag <= that.subtypeTag; + } + } + } + + enum ArgumentsArity { + ZERO(0), + ONE(1), + TWO(2), + THREE(3); + + int arity; + + ArgumentsArity(int arity) { + this.arity = arity; + } + + String asExpressionList(TypeKind type) { + StringBuilder buf = new StringBuilder(); + String sep = ""; + for (int i = 0; i < arity; i++) { + buf.append(sep); + buf.append(type.valueString); + sep = ","; + } + return buf.toString(); + } + } + + static class VarargsMethod { + TypeKind varargsElement; + + VarargsMethod(TypeKind varargsElement) { + this.varargsElement = varargsElement; + } + + @Override + public String toString() { + return "void m("+ varargsElement.typeString+ "... args) {}"; + } + + boolean isApplicable(TypeKind actual, ArgumentsArity argsArity) { + return argsArity == ArgumentsArity.ZERO || + actual.isSubtypeOf(varargsElement); + } + + boolean isMoreSpecificThan(VarargsMethod that) { + return varargsElement.isSubtypeOf(that.varargsElement); + } + } + + public static void main(String... args) throws Exception { + new T6199075().test(); + } + + void test() throws Exception { + for (TypeKind formal1 : TypeKind.values()) { + VarargsMethod m1 = new VarargsMethod(formal1); + for (TypeKind formal2 : TypeKind.values()) { + VarargsMethod m2 = new VarargsMethod(formal2); + for (TypeKind actual : TypeKind.values()) { + for (ArgumentsArity argsArity : ArgumentsArity.values()) { + compileAndCheck(m1, m2, actual, argsArity); + } + } + } + } + + System.out.println("Total checks made: " + checkCount); + System.out.println("Bytecode checks made: " + bytecodeCheckCount); + } + + void compileAndCheck(VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) throws Exception { + final JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); + JavaSource source = new JavaSource(m1, m2, actual, argsArity); + ErrorChecker ec = new ErrorChecker(); + JavacTask ct = (JavacTask)tool.getTask(null, null, ec, + null, null, Arrays.asList(source)); + ct.generate(); + check(source, ec, m1, m2, actual, argsArity); + } + + void check(JavaSource source, ErrorChecker ec, VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) { + checkCount++; + boolean resolutionError = false; + VarargsMethod selectedMethod = null; + + boolean m1_applicable = m1.isApplicable(actual, argsArity); + boolean m2_applicable = m2.isApplicable(actual, argsArity); + + if (!m1_applicable && !m2_applicable) { + resolutionError = true; + } else if (m1_applicable && m2_applicable) { + //most specific + boolean m1_moreSpecific = m1.isMoreSpecificThan(m2); + boolean m2_moreSpecific = m2.isMoreSpecificThan(m1); + resolutionError = m1_moreSpecific == m2_moreSpecific; + selectedMethod = m1_moreSpecific ? m1 : m2; + } else { + selectedMethod = m1_applicable ? + m1 : m2; + } + + if (ec.errorFound != resolutionError) { + throw new Error("invalid diagnostics for source:\n" + + source.getCharContent(true) + + "\nExpected resolution error: " + resolutionError + + "\nFound error: " + ec.errorFound + + "\nCompiler diagnostics:\n" + ec.printDiags()); + } else if (!resolutionError) { + verifyBytecode(selectedMethod); + } + } + + void verifyBytecode(VarargsMethod selected) { + bytecodeCheckCount++; + File compiledTest = new File("Test.class"); + try { + ClassFile cf = ClassFile.read(compiledTest); + Method testMethod = null; + for (Method m : cf.methods) { + if (m.getName(cf.constant_pool).equals("test")) { + testMethod = m; + break; + } + } + if (testMethod == null) { + throw new Error("Test method not found"); + } + Code_attribute ea = (Code_attribute)testMethod.attributes.get(Attribute.Code); + if (testMethod == null) { + throw new Error("Code attribute for test() method not found"); + } + + for (Instruction i : ea.getInstructions()) { + if (i.getMnemonic().equals("invokevirtual")) { + int cp_entry = i.getUnsignedShort(1); + CONSTANT_Methodref_info methRef = + (CONSTANT_Methodref_info)cf.constant_pool.get(cp_entry); + String type = methRef.getNameAndTypeInfo().getType(); + if (!type.contains(selected.varargsElement.bytecodeString)) { + throw new Error("Unexpected type method call: " + type); + } + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new Error("error reading " + compiledTest +": " + e); + } + } + + static class JavaSource extends SimpleJavaFileObject { + + static final String source_template = "class Test {\n" + + " #V1\n" + + " #V2\n" + + " void test() { m(#E); }\n" + + "}"; + + String source; + + public JavaSource(VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) { + super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); + source = source_template.replaceAll("#V1", m1.toString()). + replaceAll("#V2", m2.toString()). + replaceAll("#E", argsArity.asExpressionList(actual)); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + } + + static class ErrorChecker implements javax.tools.DiagnosticListener { + + boolean errorFound; + List errDiags = List.nil(); + + public void report(Diagnostic diagnostic) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + errDiags = errDiags.append(diagnostic.getMessage(Locale.getDefault())); + errorFound = true; + } + } + + String printDiags() { + StringBuilder buf = new StringBuilder(); + for (String s : errDiags) { + buf.append(s); + buf.append("\n"); + } + return buf.toString(); + } + } +}