7042566: Regression: new ambiguity between varargs method
authormcimadamore
Wed, 11 May 2011 13:10:57 +0200
changeset 9720 bc06a797f393
parent 9719 29adbf9e4cae
child 9721 da048be191c2
7042566: Regression: new ambiguity between varargs method Summary: Erroneous ambiguity error when choosing most specific varargs method Reviewed-by: jjg
langtools/src/share/classes/com/sun/tools/javac/comp/Infer.java
langtools/src/share/classes/com/sun/tools/javac/comp/Resolve.java
langtools/test/tools/javac/varargs/7042566/T7042566.java
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Infer.java	Tue May 10 19:53:49 2011 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Infer.java	Wed May 11 13:10:57 2011 +0200
@@ -407,9 +407,7 @@
 
         // for varargs arguments as well
         if (useVarargs) {
-            //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 elemType = types.elemtypeOrType(varargsFormal);
+            Type elemType = types.elemtype(varargsFormal);
             Type elemUndet = types.subst(elemType, tvars, undetvars);
             while (actuals.nonEmpty()) {
                 Type actual = actuals.head.baseType();
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Tue May 10 19:53:49 2011 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Wed May 11 13:10:57 2011 +0200
@@ -458,9 +458,7 @@
             throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args
 
         if (useVarargs) {
-            //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);
+            Type elt = types.elemtype(varargsFormal);
             while (argtypes.nonEmpty()) {
                 if (!types.isConvertible(argtypes.head, elt, warn))
                     throw inapplicableMethodException.setMessage("varargs.argument.mismatch",
@@ -819,10 +817,10 @@
     private boolean signatureMoreSpecific(Env<AttrContext> env, Type site, Symbol m1, Symbol m2, boolean allowBoxing, boolean useVarargs) {
         noteWarner.clear();
         Type mtype1 = types.memberType(site, adjustVarargs(m1, m2, useVarargs));
-        return (instantiate(env, site, adjustVarargs(m2, m1, useVarargs), types.lowerBoundArgtypes(mtype1), null,
-                             allowBoxing, false, noteWarner) != null ||
-                 useVarargs && instantiate(env, site, adjustVarargs(m2, m1, useVarargs), types.lowerBoundArgtypes(mtype1), null,
-                                           allowBoxing, true, noteWarner) != null) &&
+        Type mtype2 = instantiate(env, site, adjustVarargs(m2, m1, useVarargs),
+                types.lowerBoundArgtypes(mtype1), null,
+                allowBoxing, false, noteWarner);
+        return mtype2 != null &&
                 !noteWarner.hasLint(Lint.LintCategory.UNCHECKED);
     }
     //where
@@ -855,7 +853,7 @@
             //append varargs element type as last synthetic formal
             args.append(types.elemtype(varargsTypeTo));
             Type mtype = types.createMethodTypeWithParameters(to.type, args.toList());
-            return new MethodSymbol(to.flags_field, to.name, mtype, to.owner);
+            return new MethodSymbol(to.flags_field & ~VARARGS, to.name, mtype, to.owner);
         } else {
             return to;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/varargs/7042566/T7042566.java	Wed May 11 13:10:57 2011 +0200
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2011, 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 7042566
+ * @summary Unambiguous varargs method calls flagged as ambiguous
+ */
+
+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.api.JavacTool;
+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.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+public class T7042566 {
+
+    VarargsMethod m1;
+    VarargsMethod m2;
+    TypeConfiguration actuals;
+
+    T7042566(TypeConfiguration m1_conf, TypeConfiguration m2_conf, TypeConfiguration actuals) {
+        this.m1 = new VarargsMethod(m1_conf);
+        this.m2 = new VarargsMethod(m2_conf);
+        this.actuals = actuals;
+    }
+
+    void compileAndCheck() throws Exception {
+        final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
+        JavaSource source = new JavaSource();
+        ErrorChecker ec = new ErrorChecker();
+        JavacTask ct = (JavacTask)tool.getTask(null, fm, ec,
+                null, null, Arrays.asList(source));
+        ct.call();
+        check(source, ec);
+    }
+
+    void check(JavaSource source, ErrorChecker ec) {
+        checkCount++;
+        boolean resolutionError = false;
+        VarargsMethod selectedMethod = null;
+
+        boolean m1_applicable = m1.isApplicable(actuals);
+        boolean m2_applicable = m2.isApplicable(actuals);
+
+        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, source);
+        }
+    }
+
+    void verifyBytecode(VarargsMethod selected, JavaSource source) {
+        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();
+                    String sig = selected.parameterTypes.bytecodeSigStr;
+                    if (!type.contains(sig)) {
+                        throw new Error("Unexpected type method call: " + type + "" +
+                                        "\nfound: " + sig +
+                                        "\n" + source.getCharContent(true));
+                    }
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new Error("error reading " + compiledTest +": " + e);
+        }
+    }
+
+    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() {
+            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+            source = source_template.replaceAll("#V1", m1.toString()).
+                    replaceAll("#V2", m2.toString()).
+                    replaceAll("#E", actuals.expressionListStr);
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            return source;
+        }
+    }
+
+    /** global decls ***/
+
+    // Create a single file manager and reuse it for each compile to save time.
+    static StandardJavaFileManager fm = JavacTool.create().getStandardFileManager(null, null, null);
+
+    //statistics
+    static int checkCount = 0;
+    static int bytecodeCheckCount = 0;
+
+    public static void main(String... args) throws Exception {
+        for (TypeConfiguration tconf1 : TypeConfiguration.values()) {
+            for (TypeConfiguration tconf2 : TypeConfiguration.values()) {
+                for (TypeConfiguration tconf3 : TypeConfiguration.values()) {
+                    new T7042566(tconf1, tconf2, tconf3).compileAndCheck();
+                }
+            }
+        }
+
+        System.out.println("Total checks made: " + checkCount);
+        System.out.println("Bytecode checks made: " + bytecodeCheckCount);
+    }
+
+    enum TypeKind {
+        OBJECT("Object", "(Object)null", "Ljava/lang/Object;"),
+        STRING("String", "(String)null", "Ljava/lang/String;");
+
+        String typeString;
+        String valueString;
+        String bytecodeString;
+
+        TypeKind(String typeString, String valueString, String bytecodeString) {
+            this.typeString = typeString;
+            this.valueString = valueString;
+            this.bytecodeString = bytecodeString;
+        }
+
+        boolean isSubtypeOf(TypeKind that) {
+            return that == OBJECT ||
+                    (that == STRING && this == STRING);
+        }
+    }
+
+    enum TypeConfiguration {
+        A(TypeKind.OBJECT),
+        B(TypeKind.STRING),
+        AA(TypeKind.OBJECT, TypeKind.OBJECT),
+        AB(TypeKind.OBJECT, TypeKind.STRING),
+        BA(TypeKind.STRING, TypeKind.OBJECT),
+        BB(TypeKind.STRING, TypeKind.STRING),
+        AAA(TypeKind.OBJECT, TypeKind.OBJECT, TypeKind.OBJECT),
+        AAB(TypeKind.OBJECT, TypeKind.OBJECT, TypeKind.STRING),
+        ABA(TypeKind.OBJECT, TypeKind.STRING, TypeKind.OBJECT),
+        ABB(TypeKind.OBJECT, TypeKind.STRING, TypeKind.STRING),
+        BAA(TypeKind.STRING, TypeKind.OBJECT, TypeKind.OBJECT),
+        BAB(TypeKind.STRING, TypeKind.OBJECT, TypeKind.STRING),
+        BBA(TypeKind.STRING, TypeKind.STRING, TypeKind.OBJECT),
+        BBB(TypeKind.STRING, TypeKind.STRING, TypeKind.STRING);
+
+        List<TypeKind> typeKindList;
+        String expressionListStr;
+        String parameterListStr;
+        String bytecodeSigStr;
+
+        private TypeConfiguration(TypeKind... typeKindList) {
+            this.typeKindList = List.from(typeKindList);
+            expressionListStr = asExpressionList();
+            parameterListStr = asParameterList();
+            bytecodeSigStr = asBytecodeString();
+        }
+
+        private String asExpressionList() {
+            StringBuilder buf = new StringBuilder();
+            String sep = "";
+            for (TypeKind tk : typeKindList) {
+                buf.append(sep);
+                buf.append(tk.valueString);
+                sep = ",";
+            }
+            return buf.toString();
+        }
+
+        private String asParameterList() {
+            StringBuilder buf = new StringBuilder();
+            String sep = "";
+            int count = 0;
+            for (TypeKind arg : typeKindList) {
+                buf.append(sep);
+                buf.append(arg.typeString);
+                if (count == (typeKindList.size() - 1)) {
+                    buf.append("...");
+                }
+                buf.append(" ");
+                buf.append("arg" + count++);
+                sep = ",";
+            }
+            return buf.toString();
+        }
+
+        private String asBytecodeString() {
+            StringBuilder buf = new StringBuilder();
+            int count = 0;
+            for (TypeKind arg : typeKindList) {
+                if (count == (typeKindList.size() - 1)) {
+                    buf.append("[");
+                }
+                buf.append(arg.bytecodeString);
+                count++;
+            }
+            return buf.toString();
+        }
+    }
+
+    static class VarargsMethod {
+        TypeConfiguration parameterTypes;
+
+        public VarargsMethod(TypeConfiguration parameterTypes) {
+            this.parameterTypes = parameterTypes;
+        }
+
+        @Override
+        public String toString() {
+            return "void m( " + parameterTypes.parameterListStr + ") {}";
+        }
+
+        boolean isApplicable(TypeConfiguration that) {
+            List<TypeKind> actuals = that.typeKindList;
+            List<TypeKind> formals = parameterTypes.typeKindList;
+            if ((actuals.size() - formals.size()) < -1)
+                return false; //not enough args
+            for (TypeKind actual : actuals) {
+                if (!actual.isSubtypeOf(formals.head))
+                    return false; //type mismatch
+                formals = formals.tail.isEmpty() ?
+                    formals :
+                    formals.tail;
+            }
+            return true;
+        }
+
+        boolean isMoreSpecificThan(VarargsMethod that) {
+            List<TypeKind> actuals = parameterTypes.typeKindList;
+            List<TypeKind> formals = that.parameterTypes.typeKindList;
+            int checks = 0;
+            int expectedCheck = Math.max(actuals.size(), formals.size());
+            while (checks < expectedCheck) {
+                if (!actuals.head.isSubtypeOf(formals.head))
+                    return false; //type mismatch
+                formals = formals.tail.isEmpty() ?
+                    formals :
+                    formals.tail;
+                actuals = actuals.tail.isEmpty() ?
+                    actuals :
+                    actuals.tail;
+                checks++;
+            }
+            return true;
+        }
+    }
+
+    static class ErrorChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
+
+        boolean errorFound;
+        List<String> errDiags = List.nil();
+
+        public void report(Diagnostic<? extends JavaFileObject> 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();
+        }
+    }
+}