langtools/test/tools/javac/lambda/bytecode/TestLambdaBytecode.java
changeset 16564 061aa10703e8
child 17575 7693e6ff0d7d
equal deleted inserted replaced
16563:c0b12eae2488 16564:061aa10703e8
       
     1 /*
       
     2  * Copyright (c) 2013, 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 8009649
       
    27  * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods
       
    28  * @library ../../lib
       
    29  * @build JavacTestingAbstractThreadedTest
       
    30  * @run main/othervm TestLambdaBytecode
       
    31  */
       
    32 
       
    33 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
       
    34 // see JDK-8006746
       
    35 
       
    36 import com.sun.tools.classfile.Attribute;
       
    37 import com.sun.tools.classfile.BootstrapMethods_attribute;
       
    38 import com.sun.tools.classfile.ClassFile;
       
    39 import com.sun.tools.classfile.Code_attribute;
       
    40 import com.sun.tools.classfile.ConstantPool.*;
       
    41 import com.sun.tools.classfile.Instruction;
       
    42 import com.sun.tools.classfile.Method;
       
    43 
       
    44 import com.sun.tools.javac.api.JavacTaskImpl;
       
    45 
       
    46 
       
    47 import java.io.File;
       
    48 import java.net.URI;
       
    49 import java.util.ArrayList;
       
    50 import java.util.Arrays;
       
    51 import java.util.Locale;
       
    52 
       
    53 import javax.tools.Diagnostic;
       
    54 import javax.tools.JavaFileObject;
       
    55 import javax.tools.SimpleJavaFileObject;
       
    56 
       
    57 import static com.sun.tools.javac.jvm.ClassFile.*;
       
    58 
       
    59 public class TestLambdaBytecode
       
    60     extends JavacTestingAbstractThreadedTest
       
    61     implements Runnable {
       
    62 
       
    63     enum ClassKind {
       
    64         CLASS("class"),
       
    65         INTERFACE("interface");
       
    66 
       
    67         String classStr;
       
    68 
       
    69         ClassKind(String classStr) {
       
    70             this.classStr = classStr;
       
    71         }
       
    72     }
       
    73 
       
    74     enum AccessKind {
       
    75         PUBLIC("public"),
       
    76         PRIVATE("private");
       
    77 
       
    78         String accessStr;
       
    79 
       
    80         AccessKind(String accessStr) {
       
    81             this.accessStr = accessStr;
       
    82         }
       
    83     }
       
    84 
       
    85     enum StaticKind {
       
    86         STATIC("static"),
       
    87         INSTANCE("");
       
    88 
       
    89         String staticStr;
       
    90 
       
    91         StaticKind(String staticStr) {
       
    92             this.staticStr = staticStr;
       
    93         }
       
    94     }
       
    95 
       
    96     enum DefaultKind {
       
    97         DEFAULT("default"),
       
    98         NO_DEFAULT("");
       
    99 
       
   100         String defaultStr;
       
   101 
       
   102         DefaultKind(String defaultStr) {
       
   103             this.defaultStr = defaultStr;
       
   104         }
       
   105     }
       
   106 
       
   107     enum ExprKind {
       
   108         LAMBDA("Runnable r = ()->{ target(); };");
       
   109 
       
   110         String exprString;
       
   111 
       
   112         ExprKind(String exprString) {
       
   113             this.exprString = exprString;
       
   114         }
       
   115     }
       
   116 
       
   117     static class MethodKind {
       
   118         ClassKind ck;
       
   119         AccessKind ak;
       
   120         StaticKind sk;
       
   121         DefaultKind dk;
       
   122 
       
   123         MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) {
       
   124             this.ck = ck;
       
   125             this.ak = ak;
       
   126             this.sk = sk;
       
   127             this.dk = dk;
       
   128         }
       
   129 
       
   130         boolean inInterface() {
       
   131             return ck == ClassKind.INTERFACE;
       
   132         }
       
   133 
       
   134         boolean isPrivate() {
       
   135             return ak == AccessKind.PRIVATE;
       
   136         }
       
   137 
       
   138         boolean isStatic() {
       
   139             return sk == StaticKind.STATIC;
       
   140         }
       
   141 
       
   142         boolean isDefault() {
       
   143             return dk == DefaultKind.DEFAULT;
       
   144         }
       
   145 
       
   146         boolean isOK() {
       
   147             if (isDefault() && (!inInterface() || isStatic())) {
       
   148                 return false;
       
   149             } else if (inInterface() &&
       
   150                     ((!isStatic() && !isDefault()) || isPrivate())) {
       
   151                 return false;
       
   152             } else {
       
   153                 return true;
       
   154             }
       
   155         }
       
   156 
       
   157         String mods() {
       
   158             StringBuilder buf = new StringBuilder();
       
   159             buf.append(ak.accessStr);
       
   160             buf.append(' ');
       
   161             buf.append(sk.staticStr);
       
   162             buf.append(' ');
       
   163             buf.append(dk.defaultStr);
       
   164             return buf.toString();
       
   165         }
       
   166     }
       
   167 
       
   168     public static void main(String... args) throws Exception {
       
   169         for (ClassKind ck : ClassKind.values()) {
       
   170             for (AccessKind ak1 : AccessKind.values()) {
       
   171                 for (StaticKind sk1 : StaticKind.values()) {
       
   172                     for (DefaultKind dk1 : DefaultKind.values()) {
       
   173                         for (AccessKind ak2 : AccessKind.values()) {
       
   174                             for (StaticKind sk2 : StaticKind.values()) {
       
   175                                 for (DefaultKind dk2 : DefaultKind.values()) {
       
   176                                     for (ExprKind ek : ExprKind.values()) {
       
   177                                         pool.execute(new TestLambdaBytecode(ck, ak1, ak2, sk1, sk2, dk1, dk2, ek));
       
   178                                     }
       
   179                                 }
       
   180                             }
       
   181                         }
       
   182                     }
       
   183                 }
       
   184             }
       
   185         }
       
   186 
       
   187         checkAfterExec();
       
   188     }
       
   189 
       
   190     MethodKind mk1, mk2;
       
   191     ExprKind ek;
       
   192     DiagChecker dc;
       
   193 
       
   194     TestLambdaBytecode(ClassKind ck, AccessKind ak1, AccessKind ak2, StaticKind sk1,
       
   195             StaticKind sk2, DefaultKind dk1, DefaultKind dk2, ExprKind ek) {
       
   196         mk1 = new MethodKind(ck, ak1, sk1, dk1);
       
   197         mk2 = new MethodKind(ck, ak2, sk2, dk2);
       
   198         this.ek = ek;
       
   199         dc = new DiagChecker();
       
   200     }
       
   201 
       
   202     public void run() {
       
   203         int id = checkCount.incrementAndGet();
       
   204         JavaSource source = new JavaSource(id);
       
   205         JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc,
       
   206                 null, null, Arrays.asList(source));
       
   207         try {
       
   208             ct.generate();
       
   209         } catch (Throwable t) {
       
   210             t.printStackTrace();
       
   211             throw new AssertionError(
       
   212                     String.format("Error thrown when compiling following code\n%s",
       
   213                     source.source));
       
   214         }
       
   215         if (dc.diagFound) {
       
   216             boolean errorExpected = !mk1.isOK() || !mk2.isOK();
       
   217             errorExpected |= mk1.isStatic() && !mk2.isStatic();
       
   218 
       
   219             if (!errorExpected) {
       
   220                 throw new AssertionError(
       
   221                         String.format("Diags found when compiling following code\n%s\n\n%s",
       
   222                         source.source, dc.printDiags()));
       
   223             }
       
   224             return;
       
   225         }
       
   226         verifyBytecode(id, source);
       
   227     }
       
   228 
       
   229     void verifyBytecode(int id, JavaSource source) {
       
   230         File compiledTest = new File(String.format("Test%d.class", id));
       
   231         try {
       
   232             ClassFile cf = ClassFile.read(compiledTest);
       
   233             Method testMethod = null;
       
   234             for (Method m : cf.methods) {
       
   235                 if (m.getName(cf.constant_pool).equals("test")) {
       
   236                     testMethod = m;
       
   237                     break;
       
   238                 }
       
   239             }
       
   240             if (testMethod == null) {
       
   241                 throw new Error("Test method not found");
       
   242             }
       
   243             Code_attribute ea =
       
   244                     (Code_attribute)testMethod.attributes.get(Attribute.Code);
       
   245             if (testMethod == null) {
       
   246                 throw new Error("Code attribute for test() method not found");
       
   247             }
       
   248 
       
   249             int bsmIdx = -1;
       
   250 
       
   251             for (Instruction i : ea.getInstructions()) {
       
   252                 if (i.getMnemonic().equals("invokedynamic")) {
       
   253                     CONSTANT_InvokeDynamic_info indyInfo =
       
   254                          (CONSTANT_InvokeDynamic_info)cf
       
   255                             .constant_pool.get(i.getShort(1));
       
   256                     bsmIdx = indyInfo.bootstrap_method_attr_index;
       
   257                     if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType(id))) {
       
   258                         throw new
       
   259                             AssertionError("type mismatch for CONSTANT_InvokeDynamic_info " + source.source + "\n" + indyInfo.getNameAndTypeInfo().getType() + "\n" + makeIndyType(id));
       
   260                     }
       
   261                 }
       
   262             }
       
   263             if (bsmIdx == -1) {
       
   264                 throw new Error("Missing invokedynamic in generated code");
       
   265             }
       
   266 
       
   267             BootstrapMethods_attribute bsm_attr =
       
   268                     (BootstrapMethods_attribute)cf
       
   269                     .getAttribute(Attribute.BootstrapMethods);
       
   270             if (bsm_attr.bootstrap_method_specifiers.length != 1) {
       
   271                 throw new Error("Bad number of method specifiers " +
       
   272                         "in BootstrapMethods attribute");
       
   273             }
       
   274             BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
       
   275                     bsm_attr.bootstrap_method_specifiers[0];
       
   276 
       
   277             if (bsm_spec.bootstrap_arguments.length != MF_ARITY) {
       
   278                 throw new Error("Bad number of static invokedynamic args " +
       
   279                         "in BootstrapMethod attribute");
       
   280             }
       
   281 
       
   282             CONSTANT_MethodHandle_info mh =
       
   283                     (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]);
       
   284 
       
   285             boolean kindOK;
       
   286             switch (mh.reference_kind) {
       
   287                 case REF_invokeStatic: kindOK = mk2.isStatic(); break;
       
   288                 case REF_invokeSpecial: kindOK = !mk2.isStatic(); break;
       
   289                 case REF_invokeInterface: kindOK = mk2.inInterface(); break;
       
   290                 default:
       
   291                     kindOK = false;
       
   292             }
       
   293 
       
   294             if (!kindOK) {
       
   295                 throw new Error("Bad invoke kind in implementation method handle");
       
   296             }
       
   297 
       
   298             if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) {
       
   299                 throw new Error("Type mismatch in implementation method handle");
       
   300             }
       
   301         } catch (Exception e) {
       
   302             e.printStackTrace();
       
   303             throw new Error("error reading " + compiledTest +": " + e);
       
   304         }
       
   305     }
       
   306     String makeIndyType(int id) {
       
   307         StringBuilder buf = new StringBuilder();
       
   308         buf.append("(");
       
   309         if (!mk2.isStatic() || mk1.inInterface()) {
       
   310             buf.append(String.format("LTest%d;", id));
       
   311         }
       
   312         buf.append(")Ljava/lang/Runnable;");
       
   313         return buf.toString();
       
   314     }
       
   315 
       
   316     static final int MF_ARITY = 3;
       
   317     static final String MH_SIG = "()V";
       
   318 
       
   319     class JavaSource extends SimpleJavaFileObject {
       
   320 
       
   321         static final String source_template =
       
   322                 "#CK Test#ID {\n" +
       
   323                 "   #MOD1 void test() { #EK }\n" +
       
   324                 "   #MOD2 void target() { }\n" +
       
   325                 "}\n";
       
   326 
       
   327         String source;
       
   328 
       
   329         JavaSource(int id) {
       
   330             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
       
   331             source = source_template.replace("#CK", mk1.ck.classStr)
       
   332                     .replace("#MOD1", mk1.mods())
       
   333                     .replace("#MOD2", mk2.mods())
       
   334                     .replace("#EK", ek.exprString)
       
   335                     .replace("#ID", String.valueOf(id));
       
   336         }
       
   337 
       
   338         @Override
       
   339         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
       
   340             return source;
       
   341         }
       
   342     }
       
   343 
       
   344     static class DiagChecker
       
   345         implements javax.tools.DiagnosticListener<JavaFileObject> {
       
   346 
       
   347         boolean diagFound;
       
   348         ArrayList<String> diags = new ArrayList<>();
       
   349 
       
   350         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   351             diags.add(diagnostic.getMessage(Locale.getDefault()));
       
   352             diagFound = true;
       
   353         }
       
   354 
       
   355         String printDiags() {
       
   356             StringBuilder buf = new StringBuilder();
       
   357             for (String s : diags) {
       
   358                 buf.append(s);
       
   359                 buf.append("\n");
       
   360             }
       
   361             return buf.toString();
       
   362         }
       
   363     }
       
   364 
       
   365 }