test/tools/javac/lambda/TestInvokeDynamic.java

Tue, 14 May 2013 11:11:09 -0700

author
rfield
date
Tue, 14 May 2013 11:11:09 -0700
changeset 1752
c09b7234cded
parent 1676
e9d986381414
child 1774
37295244f534
permissions
-rw-r--r--

8012556: Implement lambda methods on interfaces as static
8006140: Javac NPE compiling Lambda expression on initialization expression of static field in interface
Summary: Lambdas occurring in static contexts or those not needing instance information should be generated into static methods. This has long been the case for classes. However, as a work-around to the lack of support for statics on interfaces, interface lambda methods have been generated into default methods. For lambdas in interface static contexts (fields and static methods) this causes an NPE in javac because there is no 'this'. MethodHandles now support static methods on interfaces. This changeset allows lambda methods to be generated as static interface methods. An existing bug in Hotspot (8013875) is exposed in a test when the "-esa" flag is used. This test and another test that already exposed this bug have been marked with @ignore.
Reviewed-by: mcimadamore

     1 /*
     2  * Copyright (c) 2012, 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  */
    24 /*
    25  * @test
    26  * @bug 7194586
    27  * @bug 8003280 8006694 8010404
    28  * @summary Add lambda tests
    29  *  Add back-end support for invokedynamic
    30  *  temporarily workaround combo tests are causing time out in several platforms
    31  * @library ../lib
    32  * @build JavacTestingAbstractThreadedTest
    33  * @run main/othervm TestInvokeDynamic
    34  */
    36 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
    37 // see JDK-8006746
    39 import com.sun.source.tree.MethodInvocationTree;
    40 import com.sun.source.tree.MethodTree;
    41 import com.sun.source.util.TaskEvent;
    42 import com.sun.source.util.TaskListener;
    43 import com.sun.source.util.TreeScanner;
    45 import com.sun.tools.classfile.Attribute;
    46 import com.sun.tools.classfile.BootstrapMethods_attribute;
    47 import com.sun.tools.classfile.ClassFile;
    48 import com.sun.tools.classfile.Code_attribute;
    49 import com.sun.tools.classfile.ConstantPool.*;
    50 import com.sun.tools.classfile.Instruction;
    51 import com.sun.tools.classfile.LineNumberTable_attribute;
    52 import com.sun.tools.classfile.Method;
    54 import com.sun.tools.javac.api.JavacTaskImpl;
    55 import com.sun.tools.javac.code.Symbol;
    56 import com.sun.tools.javac.code.Symbol.MethodSymbol;
    57 import com.sun.tools.javac.code.Symtab;
    58 import com.sun.tools.javac.code.Types;
    59 import com.sun.tools.javac.jvm.Pool;
    60 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
    61 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
    62 import com.sun.tools.javac.tree.JCTree.JCIdent;
    63 import com.sun.tools.javac.util.Context;
    64 import com.sun.tools.javac.util.Names;
    66 import java.io.File;
    67 import java.net.URI;
    68 import java.util.ArrayList;
    69 import java.util.Arrays;
    70 import java.util.Locale;
    72 import javax.tools.Diagnostic;
    73 import javax.tools.JavaFileObject;
    74 import javax.tools.SimpleJavaFileObject;
    76 import static com.sun.tools.javac.jvm.ClassFile.*;
    78 public class TestInvokeDynamic
    79     extends JavacTestingAbstractThreadedTest
    80     implements Runnable {
    82     enum StaticArgumentKind {
    83         STRING("Hello!", "String", "Ljava/lang/String;") {
    84             @Override
    85             boolean check(CPInfo cpInfo) throws Exception {
    86                 return (cpInfo instanceof CONSTANT_String_info) &&
    87                         ((CONSTANT_String_info)cpInfo).getString()
    88                         .equals(value);
    89             }
    90         },
    91         CLASS(null, "Class<?>", "Ljava/lang/Class;") {
    92             @Override
    93             boolean check(CPInfo cpInfo) throws Exception {
    94                 return (cpInfo instanceof CONSTANT_Class_info) &&
    95                         ((CONSTANT_Class_info)cpInfo).getName()
    96                         .equals("java/lang/String");
    97             }
    98         },
    99         INTEGER(1, "int", "I") {
   100             @Override
   101             boolean check(CPInfo cpInfo) throws Exception {
   102                 return (cpInfo instanceof CONSTANT_Integer_info) &&
   103                         ((CONSTANT_Integer_info)cpInfo).value ==
   104                         ((Integer)value).intValue();
   105             }
   106         },
   107         LONG(1L, "long", "J") {
   108             @Override
   109             boolean check(CPInfo cpInfo) throws Exception {
   110                 return (cpInfo instanceof CONSTANT_Long_info) &&
   111                         ((CONSTANT_Long_info)cpInfo).value ==
   112                         ((Long)value).longValue();
   113             }
   114         },
   115         FLOAT(1.0f, "float", "F") {
   116             @Override
   117             boolean check(CPInfo cpInfo) throws Exception {
   118                 return (cpInfo instanceof CONSTANT_Float_info) &&
   119                         ((CONSTANT_Float_info)cpInfo).value ==
   120                         ((Float)value).floatValue();
   121             }
   122         },
   123         DOUBLE(1.0, "double","D") {
   124             @Override
   125             boolean check(CPInfo cpInfo) throws Exception {
   126                 return (cpInfo instanceof CONSTANT_Double_info) &&
   127                         ((CONSTANT_Double_info)cpInfo).value ==
   128                         ((Double)value).doubleValue();
   129             }
   130         },
   131         METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") {
   132             @Override
   133             boolean check(CPInfo cpInfo) throws Exception {
   134                 if (!(cpInfo instanceof CONSTANT_MethodHandle_info))
   135                     return false;
   136                 CONSTANT_MethodHandle_info handleInfo =
   137                         (CONSTANT_MethodHandle_info)cpInfo;
   138                 return handleInfo.getCPRefInfo().getClassName().equals("Array") &&
   139                         handleInfo.reference_kind == RefKind.REF_invokeVirtual &&
   140                         handleInfo.getCPRefInfo()
   141                         .getNameAndTypeInfo().getName().equals("clone") &&
   142                         handleInfo.getCPRefInfo()
   143                         .getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;");
   144             }
   145         },
   146         METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") {
   147             @Override
   148             boolean check(CPInfo cpInfo) throws Exception {
   149                 return (cpInfo instanceof CONSTANT_MethodType_info) &&
   150                         ((CONSTANT_MethodType_info)cpInfo).getType()
   151                         .equals("()Ljava/lang/Object;");
   152             }
   153         };
   155         Object value;
   156         String sourceTypeStr;
   157         String bytecodeTypeStr;
   159         StaticArgumentKind(Object value, String sourceTypeStr,
   160                 String bytecodeTypeStr) {
   161             this.value = value;
   162             this.sourceTypeStr = sourceTypeStr;
   163             this.bytecodeTypeStr = bytecodeTypeStr;
   164         }
   166         abstract boolean check(CPInfo cpInfo) throws Exception;
   168         Object getValue(Symtab syms, Names names, Types types) {
   169             switch (this) {
   170                 case STRING:
   171                 case INTEGER:
   172                 case LONG:
   173                 case FLOAT:
   174                 case DOUBLE:
   175                     return value;
   176                 case CLASS:
   177                     return syms.stringType.tsym;
   178                 case METHOD_HANDLE:
   179                     return new Pool.MethodHandle(REF_invokeVirtual,
   180                             syms.arrayCloneMethod, types);
   181                 case METHOD_TYPE:
   182                     return syms.arrayCloneMethod.type;
   183                 default:
   184                     throw new AssertionError();
   185             }
   186         }
   187     }
   189     enum StaticArgumentsArity {
   190         ZERO(0),
   191         ONE(1),
   192         TWO(2),
   193         THREE(3);
   195         int arity;
   197         StaticArgumentsArity(int arity) {
   198             this.arity = arity;
   199         }
   200     }
   202     public static void main(String... args) throws Exception {
   203         for (StaticArgumentsArity arity : StaticArgumentsArity.values()) {
   204             if (arity.arity == 0) {
   205                 pool.execute(new TestInvokeDynamic(arity));
   206             } else {
   207                 for (StaticArgumentKind sak1 : StaticArgumentKind.values()) {
   208                     if (arity.arity == 1) {
   209                         pool.execute(new TestInvokeDynamic(arity, sak1));
   210                     } else {
   211                         for (StaticArgumentKind sak2 : StaticArgumentKind.values()) {
   212                             if (arity.arity == 2) {
   213                                 pool.execute(new TestInvokeDynamic(arity, sak1, sak2));
   214                             } else {
   215                                 for (StaticArgumentKind sak3 : StaticArgumentKind.values()) {
   216                                     pool.execute(
   217                                         new TestInvokeDynamic(arity, sak1, sak2, sak3));
   218                                 }
   219                             }
   220                         }
   221                     }
   222                 }
   223             }
   224         }
   226         checkAfterExec();
   227     }
   229     StaticArgumentsArity arity;
   230     StaticArgumentKind[] saks;
   231     DiagChecker dc;
   233     TestInvokeDynamic(StaticArgumentsArity arity, StaticArgumentKind... saks) {
   234         this.arity = arity;
   235         this.saks = saks;
   236         dc = new DiagChecker();
   237     }
   239     public void run() {
   240         int id = checkCount.incrementAndGet();
   241         JavaSource source = new JavaSource(id);
   242         JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc,
   243                 Arrays.asList("-g"), null, Arrays.asList(source));
   244         Context context = ct.getContext();
   245         Symtab syms = Symtab.instance(context);
   246         Names names = Names.instance(context);
   247         Types types = Types.instance(context);
   248         ct.addTaskListener(new Indifier(syms, names, types));
   249         try {
   250             ct.generate();
   251         } catch (Throwable t) {
   252             t.printStackTrace();
   253             throw new AssertionError(
   254                     String.format("Error thrown when compiling following code\n%s",
   255                     source.source));
   256         }
   257         if (dc.diagFound) {
   258             throw new AssertionError(
   259                     String.format("Diags found when compiling following code\n%s\n\n%s",
   260                     source.source, dc.printDiags()));
   261         }
   262         verifyBytecode(id);
   263     }
   265     void verifyBytecode(int id) {
   266         File compiledTest = new File(String.format("Test%d.class", id));
   267         try {
   268             ClassFile cf = ClassFile.read(compiledTest);
   269             Method testMethod = null;
   270             for (Method m : cf.methods) {
   271                 if (m.getName(cf.constant_pool).equals("test")) {
   272                     testMethod = m;
   273                     break;
   274                 }
   275             }
   276             if (testMethod == null) {
   277                 throw new Error("Test method not found");
   278             }
   279             Code_attribute ea =
   280                     (Code_attribute)testMethod.attributes.get(Attribute.Code);
   281             if (testMethod == null) {
   282                 throw new Error("Code attribute for test() method not found");
   283             }
   285             int bsmIdx = -1;
   287             for (Instruction i : ea.getInstructions()) {
   288                 if (i.getMnemonic().equals("invokedynamic")) {
   289                     CONSTANT_InvokeDynamic_info indyInfo =
   290                          (CONSTANT_InvokeDynamic_info)cf
   291                             .constant_pool.get(i.getShort(1));
   292                     bsmIdx = indyInfo.bootstrap_method_attr_index;
   293                     if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) {
   294                         throw new
   295                             AssertionError("type mismatch for CONSTANT_InvokeDynamic_info");
   296                     }
   297                 }
   298             }
   299             if (bsmIdx == -1) {
   300                 throw new Error("Missing invokedynamic in generated code");
   301             }
   303             BootstrapMethods_attribute bsm_attr =
   304                     (BootstrapMethods_attribute)cf
   305                     .getAttribute(Attribute.BootstrapMethods);
   306             if (bsm_attr.bootstrap_method_specifiers.length != 1) {
   307                 throw new Error("Bad number of method specifiers " +
   308                         "in BootstrapMethods attribute");
   309             }
   310             BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
   311                     bsm_attr.bootstrap_method_specifiers[0];
   313             if (bsm_spec.bootstrap_arguments.length != arity.arity) {
   314                 throw new Error("Bad number of static invokedynamic args " +
   315                         "in BootstrapMethod attribute");
   316             }
   318             int count = 0;
   319             for (StaticArgumentKind sak : saks) {
   320                 if (!sak.check(cf.constant_pool
   321                         .get(bsm_spec.bootstrap_arguments[count]))) {
   322                     throw new Error("Bad static argument value " + sak);
   323                 }
   324                 count++;
   325             }
   327             CONSTANT_MethodHandle_info bsm_handle =
   328                     (CONSTANT_MethodHandle_info)cf.constant_pool
   329                     .get(bsm_spec.bootstrap_method_ref);
   331             if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) {
   332                 throw new Error("Bad kind on boostrap method handle");
   333             }
   335             CONSTANT_Methodref_info bsm_ref =
   336                     (CONSTANT_Methodref_info)cf.constant_pool
   337                     .get(bsm_handle.reference_index);
   339             if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) {
   340                 throw new Error("Bad owner of boostrap method");
   341             }
   343             if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) {
   344                 throw new Error("Bad boostrap method name");
   345             }
   347             if (!bsm_ref.getNameAndTypeInfo()
   348                     .getType().equals(asBSMSignatureString())) {
   349                 throw new Error("Bad boostrap method type" +
   350                         bsm_ref.getNameAndTypeInfo().getType() + " " +
   351                         asBSMSignatureString());
   352             }
   354             LineNumberTable_attribute lnt =
   355                     (LineNumberTable_attribute)ea.attributes.get(Attribute.LineNumberTable);
   357             if (lnt == null) {
   358                 throw new Error("No LineNumberTable attribute");
   359             }
   360             if (lnt.line_number_table_length != 2) {
   361                 throw new Error("Wrong number of entries in LineNumberTable");
   362             }
   363         } catch (Exception e) {
   364             e.printStackTrace();
   365             throw new Error("error reading " + compiledTest +": " + e);
   366         }
   367     }
   369     String asBSMSignatureString() {
   370         StringBuilder buf = new StringBuilder();
   371         buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;");
   372         for (StaticArgumentKind sak : saks) {
   373             buf.append(sak.bytecodeTypeStr);
   374         }
   375         buf.append(")Ljava/lang/invoke/CallSite;");
   376         return buf.toString();
   377     }
   379     class JavaSource extends SimpleJavaFileObject {
   381         static final String source_template = "import java.lang.invoke.*;\n" +
   382                 "class Bootstrap {\n" +
   383                 "   public static CallSite bsm(MethodHandles.Lookup lookup, " +
   384                 "String name, MethodType methodType #SARGS) {\n" +
   385                 "       return null;\n" +
   386                 "   }\n" +
   387                 "}\n" +
   388                 "class Test#ID {\n" +
   389                 "   void m() { }\n" +
   390                 "   void test() {\n" +
   391                 "      Object o = this; // marker statement \n" +
   392                 "      m();\n" +
   393                 "   }\n" +
   394                 "}";
   396         String source;
   398         JavaSource(int id) {
   399             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
   400             source = source_template.replace("#SARGS", asSignatureString())
   401                     .replace("#ID", String.valueOf(id));
   402         }
   404         @Override
   405         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
   406             return source;
   407         }
   409         String asSignatureString() {
   410             int count = 0;
   411             StringBuilder buf = new StringBuilder();
   412             for (StaticArgumentKind sak : saks) {
   413                 buf.append(",");
   414                 buf.append(sak.sourceTypeStr);
   415                 buf.append(' ');
   416                 buf.append(String.format("x%d", count++));
   417             }
   418             return buf.toString();
   419         }
   420     }
   422     class Indifier extends TreeScanner<Void, Void> implements TaskListener {
   424         MethodSymbol bsm;
   425         Symtab syms;
   426         Names names;
   427         Types types;
   429         Indifier(Symtab syms, Names names, Types types) {
   430             this.syms = syms;
   431             this.names = names;
   432             this.types = types;
   433         }
   435         @Override
   436         public void started(TaskEvent e) {
   437             //do nothing
   438         }
   440         @Override
   441         public void finished(TaskEvent e) {
   442             if (e.getKind() == TaskEvent.Kind.ANALYZE) {
   443                 scan(e.getCompilationUnit(), null);
   444             }
   445         }
   447         @Override
   448         public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
   449             super.visitMethodInvocation(node, p);
   450             JCMethodInvocation apply = (JCMethodInvocation)node;
   451             JCIdent ident = (JCIdent)apply.meth;
   452             Symbol oldSym = ident.sym;
   453             if (!oldSym.isConstructor()) {
   454                 Object[] staticArgs = new Object[arity.arity];
   455                 for (int i = 0; i < arity.arity ; i++) {
   456                     staticArgs[i] = saks[i].getValue(syms, names, types);
   457                 }
   458                 ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name,
   459                         oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs);
   460             }
   461             return null;
   462         }
   464         @Override
   465         public Void visitMethod(MethodTree node, Void p) {
   466             super.visitMethod(node, p);
   467             if (node.getName().toString().equals("bsm")) {
   468                 bsm = ((JCMethodDecl)node).sym;
   469             }
   470             return null;
   471         }
   472     }
   474     static class DiagChecker
   475         implements javax.tools.DiagnosticListener<JavaFileObject> {
   477         boolean diagFound;
   478         ArrayList<String> diags = new ArrayList<>();
   480         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
   481             diags.add(diagnostic.getMessage(Locale.getDefault()));
   482             diagFound = true;
   483         }
   485         String printDiags() {
   486             StringBuilder buf = new StringBuilder();
   487             for (String s : diags) {
   488                 buf.append(s);
   489                 buf.append("\n");
   490             }
   491             return buf.toString();
   492         }
   493     }
   495 }

mercurial