6199075: Unambiguous varargs method calls flagged as ambiguous

Fri, 10 Dec 2010 15:24:17 +0000

author
mcimadamore
date
Fri, 10 Dec 2010 15:24:17 +0000
changeset 787
b1c98bfd4709
parent 786
2ca5866a8dfb
child 788
8ec3a824f925

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

src/share/classes/com/sun/tools/javac/code/Types.java file | annotate | diff | comparison | revisions
src/share/classes/com/sun/tools/javac/comp/Resolve.java file | annotate | diff | comparison | revisions
test/tools/javac/varargs/6199075/T6199075.java file | annotate | diff | comparison | revisions
     1.1 --- a/src/share/classes/com/sun/tools/javac/code/Types.java	Fri Dec 10 15:23:42 2010 +0000
     1.2 +++ b/src/share/classes/com/sun/tools/javac/code/Types.java	Fri Dec 10 15:24:17 2010 +0000
     1.3 @@ -1318,6 +1318,13 @@
     1.4          }
     1.5      }
     1.6  
     1.7 +    public Type elemtypeOrType(Type t) {
     1.8 +        Type elemtype = elemtype(t);
     1.9 +        return elemtype != null ?
    1.10 +            elemtype :
    1.11 +            t;
    1.12 +    }
    1.13 +
    1.14      /**
    1.15       * Mapping to take element type of an arraytype
    1.16       */
     2.1 --- a/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Fri Dec 10 15:23:42 2010 +0000
     2.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Resolve.java	Fri Dec 10 15:24:17 2010 +0000
     2.3 @@ -470,7 +470,9 @@
     2.4              throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args
     2.5  
     2.6          if (useVarargs) {
     2.7 -            Type elt = types.elemtype(varargsFormal);
     2.8 +            //note: if applicability check is triggered by most specific test,
     2.9 +            //the last argument of a varargs is _not_ an array type (see JLS 15.12.2.5)
    2.10 +            Type elt = types.elemtypeOrType(varargsFormal);
    2.11              while (argtypes.nonEmpty()) {
    2.12                  if (!types.isConvertible(argtypes.head, elt, warn))
    2.13                      throw inapplicableMethodException.setMessage("varargs.argument.mismatch",
    2.14 @@ -827,24 +829,30 @@
    2.15          List<Type> fromArgs = from.type.getParameterTypes();
    2.16          List<Type> toArgs = to.type.getParameterTypes();
    2.17          if (useVarargs &&
    2.18 -                toArgs.length() < fromArgs.length() &&
    2.19                  (from.flags() & VARARGS) != 0 &&
    2.20                  (to.flags() & VARARGS) != 0) {
    2.21 -            //if we are checking a varargs method 'from' against another varargs
    2.22 -            //method 'to' (where arity of 'to' < arity of 'from') then expand signature
    2.23 -            //of 'to' to 'fit' arity of 'from' (this means adding fake formals to 'to'
    2.24 -            //until 'to' signature has the same arity as 'from')
    2.25 -            ListBuffer<Type> args = ListBuffer.lb();
    2.26              Type varargsTypeFrom = fromArgs.last();
    2.27              Type varargsTypeTo = toArgs.last();
    2.28 -            while (fromArgs.head != varargsTypeFrom) {
    2.29 -                args.append(toArgs.head == varargsTypeTo ? types.elemtype(varargsTypeTo) : toArgs.head);
    2.30 -                fromArgs = fromArgs.tail;
    2.31 -                toArgs = toArgs.head == varargsTypeTo ?
    2.32 -                    toArgs :
    2.33 -                    toArgs.tail;
    2.34 +            ListBuffer<Type> args = ListBuffer.lb();
    2.35 +            if (toArgs.length() < fromArgs.length()) {
    2.36 +                //if we are checking a varargs method 'from' against another varargs
    2.37 +                //method 'to' (where arity of 'to' < arity of 'from') then expand signature
    2.38 +                //of 'to' to 'fit' arity of 'from' (this means adding fake formals to 'to'
    2.39 +                //until 'to' signature has the same arity as 'from')
    2.40 +                while (fromArgs.head != varargsTypeFrom) {
    2.41 +                    args.append(toArgs.head == varargsTypeTo ? types.elemtype(varargsTypeTo) : toArgs.head);
    2.42 +                    fromArgs = fromArgs.tail;
    2.43 +                    toArgs = toArgs.head == varargsTypeTo ?
    2.44 +                        toArgs :
    2.45 +                        toArgs.tail;
    2.46 +                }
    2.47 +            } else {
    2.48 +                //formal argument list is same as original list where last
    2.49 +                //argument (array type) is removed
    2.50 +                args.appendList(toArgs.reverse().tail.reverse());
    2.51              }
    2.52 -            args.append(varargsTypeTo);
    2.53 +            //append varargs element type as last synthetic formal
    2.54 +            args.append(types.elemtype(varargsTypeTo));
    2.55              MethodSymbol msym = new MethodSymbol(to.flags_field,
    2.56                                                   to.name,
    2.57                                                   (Type)to.type.clone(), //see: 6990136
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/test/tools/javac/varargs/6199075/T6199075.java	Fri Dec 10 15:24:17 2010 +0000
     3.3 @@ -0,0 +1,284 @@
     3.4 +/*
     3.5 + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
     3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3.7 + *
     3.8 + * This code is free software; you can redistribute it and/or modify it
     3.9 + * under the terms of the GNU General Public License version 2 only, as
    3.10 + * published by the Free Software Foundation.
    3.11 + *
    3.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
    3.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    3.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    3.15 + * version 2 for more details (a copy is included in the LICENSE file that
    3.16 + * accompanied this code).
    3.17 + *
    3.18 + * You should have received a copy of the GNU General Public License version
    3.19 + * 2 along with this work; if not, write to the Free Software Foundation,
    3.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    3.21 + *
    3.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    3.23 + * or visit www.oracle.com if you need additional information or have any
    3.24 + * questions.
    3.25 + */
    3.26 +
    3.27 +/*
    3.28 + * @test
    3.29 + * @bug 6199075
    3.30 + *
    3.31 + * @summary Unambiguous varargs method calls flagged as ambiguous
    3.32 + * @author mcimadamore
    3.33 + *
    3.34 + */
    3.35 +
    3.36 +import com.sun.source.util.JavacTask;
    3.37 +import com.sun.tools.classfile.Instruction;
    3.38 +import com.sun.tools.classfile.Attribute;
    3.39 +import com.sun.tools.classfile.ClassFile;
    3.40 +import com.sun.tools.classfile.Code_attribute;
    3.41 +import com.sun.tools.classfile.ConstantPool.*;
    3.42 +import com.sun.tools.classfile.Method;
    3.43 +import com.sun.tools.javac.util.List;
    3.44 +
    3.45 +import java.io.File;
    3.46 +import java.net.URI;
    3.47 +import java.util.Arrays;
    3.48 +import java.util.Locale;
    3.49 +import javax.tools.Diagnostic;
    3.50 +import javax.tools.JavaCompiler;
    3.51 +import javax.tools.JavaFileObject;
    3.52 +import javax.tools.SimpleJavaFileObject;
    3.53 +import javax.tools.ToolProvider;
    3.54 +
    3.55 +public class T6199075 {
    3.56 +
    3.57 +    int checkCount = 0;
    3.58 +    int bytecodeCheckCount = 0;
    3.59 +
    3.60 +    enum TypeKind {
    3.61 +        BYTE("byte", "(byte)1", "[B", 0),
    3.62 +        CHAR("char", "'c'", "[C", 1),
    3.63 +        SHORT("short", "(short)1", "[S", 2),
    3.64 +        INT("int", "1", "[I", 3),
    3.65 +        LONG("long", "1L", "[J", 4),
    3.66 +        FLOAT("float", "1.0F", "[F", 5),
    3.67 +        DOUBLE("double", "1.0D", "[D", 6),
    3.68 +        BOOLEAN("boolean", "true", "[Z", -1);
    3.69 +
    3.70 +        String typeString;
    3.71 +        String valueString;
    3.72 +        String bytecodeString;
    3.73 +        private int subtypeTag;
    3.74 +
    3.75 +        TypeKind(String typeString, String valueString, String bytecodeString, int subtypeTag) {
    3.76 +            this.typeString = typeString;
    3.77 +            this.valueString = valueString;
    3.78 +            this.bytecodeString = bytecodeString;
    3.79 +            this.subtypeTag = subtypeTag;
    3.80 +        }
    3.81 +
    3.82 +        boolean isSubtypeOf(TypeKind that) {
    3.83 +            switch (this) {
    3.84 +                case BOOLEAN:
    3.85 +                    return that == BOOLEAN;
    3.86 +                case BYTE:
    3.87 +                case CHAR:
    3.88 +                    return this.subtypeTag == that.subtypeTag ||
    3.89 +                            this.subtypeTag + 2 <= that.subtypeTag;
    3.90 +                default:
    3.91 +                    return this.subtypeTag <= that.subtypeTag;
    3.92 +            }
    3.93 +        }
    3.94 +    }
    3.95 +
    3.96 +    enum ArgumentsArity {
    3.97 +        ZERO(0),
    3.98 +        ONE(1),
    3.99 +        TWO(2),
   3.100 +        THREE(3);
   3.101 +
   3.102 +        int arity;
   3.103 +
   3.104 +        ArgumentsArity(int arity) {
   3.105 +            this.arity = arity;
   3.106 +        }
   3.107 +
   3.108 +        String asExpressionList(TypeKind type) {
   3.109 +            StringBuilder buf = new StringBuilder();
   3.110 +            String sep = "";
   3.111 +            for (int i = 0; i < arity; i++) {
   3.112 +                buf.append(sep);
   3.113 +                buf.append(type.valueString);
   3.114 +                sep = ",";
   3.115 +            }
   3.116 +            return buf.toString();
   3.117 +        }
   3.118 +    }
   3.119 +
   3.120 +    static class VarargsMethod {
   3.121 +        TypeKind varargsElement;
   3.122 +
   3.123 +        VarargsMethod(TypeKind varargsElement) {
   3.124 +            this.varargsElement = varargsElement;
   3.125 +        }
   3.126 +
   3.127 +        @Override
   3.128 +        public String toString() {
   3.129 +            return "void m("+ varargsElement.typeString+ "... args) {}";
   3.130 +        }
   3.131 +
   3.132 +        boolean isApplicable(TypeKind actual, ArgumentsArity argsArity) {
   3.133 +            return argsArity == ArgumentsArity.ZERO ||
   3.134 +                    actual.isSubtypeOf(varargsElement);
   3.135 +        }
   3.136 +
   3.137 +        boolean isMoreSpecificThan(VarargsMethod that) {
   3.138 +            return varargsElement.isSubtypeOf(that.varargsElement);
   3.139 +        }
   3.140 +    }
   3.141 +
   3.142 +    public static void main(String... args) throws Exception {
   3.143 +        new T6199075().test();
   3.144 +    }
   3.145 +
   3.146 +    void test() throws Exception {
   3.147 +        for (TypeKind formal1 : TypeKind.values()) {
   3.148 +            VarargsMethod m1 = new VarargsMethod(formal1);
   3.149 +            for (TypeKind formal2 : TypeKind.values()) {
   3.150 +                VarargsMethod m2 = new VarargsMethod(formal2);
   3.151 +                for (TypeKind actual : TypeKind.values()) {
   3.152 +                    for (ArgumentsArity argsArity : ArgumentsArity.values()) {
   3.153 +                        compileAndCheck(m1, m2, actual, argsArity);
   3.154 +                    }
   3.155 +                }
   3.156 +            }
   3.157 +        }
   3.158 +
   3.159 +        System.out.println("Total checks made: " + checkCount);
   3.160 +        System.out.println("Bytecode checks made: " + bytecodeCheckCount);
   3.161 +    }
   3.162 +
   3.163 +    void compileAndCheck(VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) throws Exception {
   3.164 +        final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
   3.165 +        JavaSource source = new JavaSource(m1, m2, actual, argsArity);
   3.166 +        ErrorChecker ec = new ErrorChecker();
   3.167 +        JavacTask ct = (JavacTask)tool.getTask(null, null, ec,
   3.168 +                null, null, Arrays.asList(source));
   3.169 +        ct.generate();
   3.170 +        check(source, ec, m1, m2, actual, argsArity);
   3.171 +    }
   3.172 +
   3.173 +    void check(JavaSource source, ErrorChecker ec, VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) {
   3.174 +        checkCount++;
   3.175 +        boolean resolutionError = false;
   3.176 +        VarargsMethod selectedMethod = null;
   3.177 +
   3.178 +        boolean m1_applicable = m1.isApplicable(actual, argsArity);
   3.179 +        boolean m2_applicable = m2.isApplicable(actual, argsArity);
   3.180 +
   3.181 +        if (!m1_applicable && !m2_applicable) {
   3.182 +            resolutionError = true;
   3.183 +        } else if (m1_applicable && m2_applicable) {
   3.184 +            //most specific
   3.185 +            boolean m1_moreSpecific = m1.isMoreSpecificThan(m2);
   3.186 +            boolean m2_moreSpecific = m2.isMoreSpecificThan(m1);
   3.187 +            resolutionError = m1_moreSpecific == m2_moreSpecific;
   3.188 +            selectedMethod = m1_moreSpecific ? m1 : m2;
   3.189 +        } else {
   3.190 +            selectedMethod = m1_applicable ?
   3.191 +                m1 : m2;
   3.192 +        }
   3.193 +
   3.194 +        if (ec.errorFound != resolutionError) {
   3.195 +            throw new Error("invalid diagnostics for source:\n" +
   3.196 +                    source.getCharContent(true) +
   3.197 +                    "\nExpected resolution error: " + resolutionError +
   3.198 +                    "\nFound error: " + ec.errorFound +
   3.199 +                    "\nCompiler diagnostics:\n" + ec.printDiags());
   3.200 +        } else if (!resolutionError) {
   3.201 +            verifyBytecode(selectedMethod);
   3.202 +        }
   3.203 +    }
   3.204 +
   3.205 +    void verifyBytecode(VarargsMethod selected) {
   3.206 +        bytecodeCheckCount++;
   3.207 +        File compiledTest = new File("Test.class");
   3.208 +        try {
   3.209 +            ClassFile cf = ClassFile.read(compiledTest);
   3.210 +            Method testMethod = null;
   3.211 +            for (Method m : cf.methods) {
   3.212 +                if (m.getName(cf.constant_pool).equals("test")) {
   3.213 +                    testMethod = m;
   3.214 +                    break;
   3.215 +                }
   3.216 +            }
   3.217 +            if (testMethod == null) {
   3.218 +                throw new Error("Test method not found");
   3.219 +            }
   3.220 +            Code_attribute ea = (Code_attribute)testMethod.attributes.get(Attribute.Code);
   3.221 +            if (testMethod == null) {
   3.222 +                throw new Error("Code attribute for test() method not found");
   3.223 +            }
   3.224 +
   3.225 +            for (Instruction i : ea.getInstructions()) {
   3.226 +                if (i.getMnemonic().equals("invokevirtual")) {
   3.227 +                    int cp_entry = i.getUnsignedShort(1);
   3.228 +                    CONSTANT_Methodref_info methRef =
   3.229 +                            (CONSTANT_Methodref_info)cf.constant_pool.get(cp_entry);
   3.230 +                    String type = methRef.getNameAndTypeInfo().getType();
   3.231 +                    if (!type.contains(selected.varargsElement.bytecodeString)) {
   3.232 +                        throw new Error("Unexpected type method call: " + type);
   3.233 +                    }
   3.234 +                    break;
   3.235 +                }
   3.236 +            }
   3.237 +        } catch (Exception e) {
   3.238 +            e.printStackTrace();
   3.239 +            throw new Error("error reading " + compiledTest +": " + e);
   3.240 +        }
   3.241 +    }
   3.242 +
   3.243 +    static class JavaSource extends SimpleJavaFileObject {
   3.244 +
   3.245 +        static final String source_template = "class Test {\n" +
   3.246 +                "   #V1\n" +
   3.247 +                "   #V2\n" +
   3.248 +                "   void test() { m(#E); }\n" +
   3.249 +                "}";
   3.250 +
   3.251 +        String source;
   3.252 +
   3.253 +        public JavaSource(VarargsMethod m1, VarargsMethod m2, TypeKind actual, ArgumentsArity argsArity) {
   3.254 +            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
   3.255 +            source = source_template.replaceAll("#V1", m1.toString()).
   3.256 +                    replaceAll("#V2", m2.toString()).
   3.257 +                    replaceAll("#E", argsArity.asExpressionList(actual));
   3.258 +        }
   3.259 +
   3.260 +        @Override
   3.261 +        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
   3.262 +            return source;
   3.263 +        }
   3.264 +    }
   3.265 +
   3.266 +    static class ErrorChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
   3.267 +
   3.268 +        boolean errorFound;
   3.269 +        List<String> errDiags = List.nil();
   3.270 +
   3.271 +        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
   3.272 +            if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
   3.273 +                errDiags = errDiags.append(diagnostic.getMessage(Locale.getDefault()));
   3.274 +                errorFound = true;
   3.275 +            }
   3.276 +        }
   3.277 +
   3.278 +        String printDiags() {
   3.279 +            StringBuilder buf = new StringBuilder();
   3.280 +            for (String s : errDiags) {
   3.281 +                buf.append(s);
   3.282 +                buf.append("\n");
   3.283 +            }
   3.284 +            return buf.toString();
   3.285 +        }
   3.286 +    }
   3.287 +}

mercurial