1 /*
   2  * Copyright (c) 2011, 2015, 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 7042566 8006694
  27  * @summary Unambiguous varargs method calls flagged as ambiguous
  28  *  temporarily workaround combo tests are causing time out in several platforms
  29  * @library ../../lib
  30  * @modules jdk.compiler/com.sun.tools.classfile
  31  *          jdk.compiler/com.sun.tools.javac.util
  32  * @build JavacTestingAbstractThreadedTest
  33  * @run main/othervm T7042566
  34  */
  35 
  36 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
  37 // see JDK-8006746
  38 
  39 import java.io.File;
  40 import java.net.URI;
  41 import java.util.Arrays;
  42 import java.util.Locale;
  43 import java.util.concurrent.atomic.AtomicInteger;
  44 import javax.tools.Diagnostic;
  45 import javax.tools.JavaCompiler;
  46 import javax.tools.JavaFileObject;
  47 import javax.tools.SimpleJavaFileObject;
  48 import javax.tools.ToolProvider;
  49 
  50 import com.sun.source.util.JavacTask;
  51 import com.sun.tools.classfile.Instruction;
  52 import com.sun.tools.classfile.Attribute;
  53 import com.sun.tools.classfile.ClassFile;
  54 import com.sun.tools.classfile.Code_attribute;
  55 import com.sun.tools.classfile.ConstantPool.*;
  56 import com.sun.tools.classfile.Method;
  57 import com.sun.tools.javac.util.List;
  58 
  59 public class T7042566
  60     extends JavacTestingAbstractThreadedTest
  61     implements Runnable {
  62 
  63     VarargsMethod m1;
  64     VarargsMethod m2;
  65     TypeConfiguration actuals;
  66 
  67     T7042566(TypeConfiguration m1_conf, TypeConfiguration m2_conf,
  68             TypeConfiguration actuals) {
  69         this.m1 = new VarargsMethod(m1_conf);
  70         this.m2 = new VarargsMethod(m2_conf);
  71         this.actuals = actuals;
  72     }
  73 
  74     @Override
  75     public void run() {
  76         int id = checkCount.incrementAndGet();
  77         final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
  78         JavaSource source = new JavaSource(id);
  79         ErrorChecker ec = new ErrorChecker();
  80         JavacTask ct = (JavacTask)tool.getTask(null, fm.get(), ec,
  81                 null, null, Arrays.asList(source));
  82         ct.call();
  83         check(source, ec, id);
  84     }
  85 
  86     void check(JavaSource source, ErrorChecker ec, int id) {
  87         boolean resolutionError = false;
  88         VarargsMethod selectedMethod = null;
  89 
  90         boolean m1_applicable = m1.isApplicable(actuals);
  91         boolean m2_applicable = m2.isApplicable(actuals);
  92 
  93         if (!m1_applicable && !m2_applicable) {
  94             resolutionError = true;
  95         } else if (m1_applicable && m2_applicable) {
  96             //most specific
  97             boolean m1_moreSpecific = m1.isMoreSpecificThan(m2);
  98             boolean m2_moreSpecific = m2.isMoreSpecificThan(m1);
  99 
 100             resolutionError = m1_moreSpecific == m2_moreSpecific;
 101             selectedMethod = m1_moreSpecific ? m1 : m2;
 102         } else {
 103             selectedMethod = m1_applicable ?
 104                 m1 : m2;
 105         }
 106 
 107         if (ec.errorFound != resolutionError) {
 108             throw new Error("invalid diagnostics for source:\n" +
 109                     source.getCharContent(true) +
 110                     "\nExpected resolution error: " + resolutionError +
 111                     "\nFound error: " + ec.errorFound +
 112                     "\nCompiler diagnostics:\n" + ec.printDiags());
 113         } else if (!resolutionError) {
 114             verifyBytecode(selectedMethod, source, id);
 115         }
 116     }
 117 
 118     void verifyBytecode(VarargsMethod selected, JavaSource source, int id) {
 119         bytecodeCheckCount.incrementAndGet();
 120         File compiledTest = new File(String.format("Test%d.class", id));
 121         try {
 122             ClassFile cf = ClassFile.read(compiledTest);
 123             Method testMethod = null;
 124             for (Method m : cf.methods) {
 125                 if (m.getName(cf.constant_pool).equals("test")) {
 126                     testMethod = m;
 127                     break;
 128                 }
 129             }
 130             if (testMethod == null) {
 131                 throw new Error("Test method not found");
 132             }
 133             Code_attribute ea =
 134                 (Code_attribute)testMethod.attributes.get(Attribute.Code);
 135             if (testMethod == null) {
 136                 throw new Error("Code attribute for test() method not found");
 137             }
 138 
 139             for (Instruction i : ea.getInstructions()) {
 140                 if (i.getMnemonic().equals("invokevirtual")) {
 141                     int cp_entry = i.getUnsignedShort(1);
 142                     CONSTANT_Methodref_info methRef =
 143                         (CONSTANT_Methodref_info)cf.constant_pool.get(cp_entry);
 144                     String type = methRef.getNameAndTypeInfo().getType();
 145                     String sig = selected.parameterTypes.bytecodeSigStr;
 146                     if (!type.contains(sig)) {
 147                         throw new Error("Unexpected type method call: " +
 148                                         type + "" +
 149                                         "\nfound: " + sig +
 150                                         "\n" + source.getCharContent(true));
 151                     }
 152                     break;
 153                 }
 154             }
 155         } catch (Exception e) {
 156             e.printStackTrace();
 157             throw new Error("error reading " + compiledTest +": " + e);
 158         }
 159     }
 160 
 161     class JavaSource extends SimpleJavaFileObject {
 162 
 163         static final String source_template = "class Test#ID {\n" +
 164                 "   #V1\n" +
 165                 "   #V2\n" +
 166                 "   void test() { m(#E); }\n" +
 167                 "}";
 168 
 169         String source;
 170 
 171         public JavaSource(int id) {
 172             super(URI.create(String.format("myfo:/Test%d.java", id)),
 173                     JavaFileObject.Kind.SOURCE);
 174             source = source_template.replaceAll("#V1", m1.toString())
 175                     .replaceAll("#V2", m2.toString())
 176                     .replaceAll("#E", actuals.expressionListStr)
 177                     .replaceAll("#ID", String.valueOf(id));
 178         }
 179 
 180         @Override
 181         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
 182             return source;
 183         }
 184     }
 185 
 186     public static void main(String... args) throws Exception {
 187         for (TypeConfiguration tconf1 : TypeConfiguration.values()) {
 188             for (TypeConfiguration tconf2 : TypeConfiguration.values()) {
 189                 for (TypeConfiguration tconf3 : TypeConfiguration.values()) {
 190                     pool.execute(new T7042566(tconf1, tconf2, tconf3));
 191                 }
 192             }
 193         }
 194 
 195         outWriter.println("Bytecode checks made: " + bytecodeCheckCount.get());
 196         checkAfterExec();
 197     }
 198 
 199     enum TypeKind {
 200         OBJECT("Object", "(Object)null", "Ljava/lang/Object;"),
 201         STRING("String", "(String)null", "Ljava/lang/String;");
 202 
 203         String typeString;
 204         String valueString;
 205         String bytecodeString;
 206 
 207         TypeKind(String typeString, String valueString, String bytecodeString) {
 208             this.typeString = typeString;
 209             this.valueString = valueString;
 210             this.bytecodeString = bytecodeString;
 211         }
 212 
 213         boolean isSubtypeOf(TypeKind that) {
 214             return that == OBJECT ||
 215                     (that == STRING && this == STRING);
 216         }
 217     }
 218 
 219     enum TypeConfiguration {
 220         A(TypeKind.OBJECT),
 221         B(TypeKind.STRING),
 222         AA(TypeKind.OBJECT, TypeKind.OBJECT),
 223         AB(TypeKind.OBJECT, TypeKind.STRING),
 224         BA(TypeKind.STRING, TypeKind.OBJECT),
 225         BB(TypeKind.STRING, TypeKind.STRING),
 226         AAA(TypeKind.OBJECT, TypeKind.OBJECT, TypeKind.OBJECT),
 227         AAB(TypeKind.OBJECT, TypeKind.OBJECT, TypeKind.STRING),
 228         ABA(TypeKind.OBJECT, TypeKind.STRING, TypeKind.OBJECT),
 229         ABB(TypeKind.OBJECT, TypeKind.STRING, TypeKind.STRING),
 230         BAA(TypeKind.STRING, TypeKind.OBJECT, TypeKind.OBJECT),
 231         BAB(TypeKind.STRING, TypeKind.OBJECT, TypeKind.STRING),
 232         BBA(TypeKind.STRING, TypeKind.STRING, TypeKind.OBJECT),
 233         BBB(TypeKind.STRING, TypeKind.STRING, TypeKind.STRING);
 234 
 235         List<TypeKind> typeKindList;
 236         String expressionListStr;
 237         String parameterListStr;
 238         String bytecodeSigStr;
 239 
 240         private TypeConfiguration(TypeKind... typeKindList) {
 241             this.typeKindList = List.from(typeKindList);
 242             expressionListStr = asExpressionList();
 243             parameterListStr = asParameterList();
 244             bytecodeSigStr = asBytecodeString();
 245         }
 246 
 247         private String asExpressionList() {
 248             StringBuilder buf = new StringBuilder();
 249             String sep = "";
 250             for (TypeKind tk : typeKindList) {
 251                 buf.append(sep);
 252                 buf.append(tk.valueString);
 253                 sep = ",";
 254             }
 255             return buf.toString();
 256         }
 257 
 258         private String asParameterList() {
 259             StringBuilder buf = new StringBuilder();
 260             String sep = "";
 261             int count = 0;
 262             for (TypeKind arg : typeKindList) {
 263                 buf.append(sep);
 264                 buf.append(arg.typeString);
 265                 if (count == (typeKindList.size() - 1)) {
 266                     buf.append("...");
 267                 }
 268                 buf.append(" ");
 269                 buf.append("arg" + count++);
 270                 sep = ",";
 271             }
 272             return buf.toString();
 273         }
 274 
 275         private String asBytecodeString() {
 276             StringBuilder buf = new StringBuilder();
 277             int count = 0;
 278             for (TypeKind arg : typeKindList) {
 279                 if (count == (typeKindList.size() - 1)) {
 280                     buf.append("[");
 281                 }
 282                 buf.append(arg.bytecodeString);
 283                 count++;
 284             }
 285             return buf.toString();
 286         }
 287     }
 288 
 289     static class VarargsMethod {
 290         TypeConfiguration parameterTypes;
 291 
 292         public VarargsMethod(TypeConfiguration parameterTypes) {
 293             this.parameterTypes = parameterTypes;
 294         }
 295 
 296         @Override
 297         public String toString() {
 298             return "void m( " + parameterTypes.parameterListStr + ") {}";
 299         }
 300 
 301         boolean isApplicable(TypeConfiguration that) {
 302             List<TypeKind> actuals = that.typeKindList;
 303             List<TypeKind> formals = parameterTypes.typeKindList;
 304             if ((actuals.size() - formals.size()) < -1)
 305                 return false; //not enough args
 306             for (TypeKind actual : actuals) {
 307                 if (!actual.isSubtypeOf(formals.head))
 308                     return false; //type mismatch
 309                 formals = formals.tail.isEmpty() ?
 310                     formals :
 311                     formals.tail;
 312             }
 313             return true;
 314         }
 315 
 316         boolean isMoreSpecificThan(VarargsMethod that) {
 317             List<TypeKind> actuals = parameterTypes.typeKindList;
 318             List<TypeKind> formals = that.parameterTypes.typeKindList;
 319             int checks = 0;
 320             int expectedCheck = Math.max(actuals.size(), formals.size());
 321             while (checks < expectedCheck) {
 322                 if (!actuals.head.isSubtypeOf(formals.head))
 323                     return false; //type mismatch
 324                 formals = formals.tail.isEmpty() ?
 325                     formals :
 326                     formals.tail;
 327                 actuals = actuals.tail.isEmpty() ?
 328                     actuals :
 329                     actuals.tail;
 330                 checks++;
 331             }
 332             return true;
 333         }
 334     }
 335 
 336     static class ErrorChecker
 337         implements javax.tools.DiagnosticListener<JavaFileObject> {
 338 
 339         boolean errorFound;
 340         List<String> errDiags = List.nil();
 341 
 342         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
 343             if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
 344                 errDiags = errDiags
 345                         .append(diagnostic.getMessage(Locale.getDefault()));
 346                 errorFound = true;
 347             }
 348         }
 349 
 350         String printDiags() {
 351             StringBuilder buf = new StringBuilder();
 352             for (String s : errDiags) {
 353                 buf.append(s);
 354                 buf.append("\n");
 355             }
 356             return buf.toString();
 357         }
 358     }
 359 
 360     //number of bytecode checks made while running combo tests
 361     static AtomicInteger bytecodeCheckCount = new AtomicInteger();
 362 
 363 }