1 /*
2  * Copyright (c) 2012, 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 7194586 8003280 8006694 8010404 8129962
27  * @summary Add lambda tests
28  *  Add back-end support for invokedynamic
29  *  temporarily workaround combo tests are causing time out in several platforms
30  * @library /tools/javac/lib
31  * @modules jdk.jdeps/com.sun.tools.classfile
32  *          jdk.compiler/com.sun.tools.javac.api
33  *          jdk.compiler/com.sun.tools.javac.code
34  *          jdk.compiler/com.sun.tools.javac.comp
35  *          jdk.compiler/com.sun.tools.javac.main
36  *          jdk.compiler/com.sun.tools.javac.jvm
37  *          jdk.compiler/com.sun.tools.javac.tree
38  *          jdk.compiler/com.sun.tools.javac.util
39  * @build combo.ComboTestHelper
40  * @run main TestInvokeDynamic
41  */
42 
43 import java.io.IOException;
44 import java.io.InputStream;
45 
46 import javax.tools.JavaFileObject;
47 
48 import com.sun.source.tree.MethodInvocationTree;
49 import com.sun.source.tree.MethodTree;
50 import com.sun.source.util.TaskEvent;
51 import com.sun.source.util.TaskListener;
52 import com.sun.source.util.TreeScanner;
53 
54 import com.sun.tools.classfile.Attribute;
55 import com.sun.tools.classfile.BootstrapMethods_attribute;
56 import com.sun.tools.classfile.ClassFile;
57 import com.sun.tools.classfile.Code_attribute;
58 import com.sun.tools.classfile.ConstantPool.*;
59 import com.sun.tools.classfile.Instruction;
60 import com.sun.tools.classfile.LineNumberTable_attribute;
61 import com.sun.tools.classfile.Method;
62 
63 import com.sun.tools.javac.api.JavacTaskImpl;
64 import com.sun.tools.javac.code.Symbol;
65 import com.sun.tools.javac.code.Symbol.MethodSymbol;
66 import com.sun.tools.javac.code.Symtab;
67 import com.sun.tools.javac.code.Types;
68 import com.sun.tools.javac.jvm.Pool;
69 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
70 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
71 import com.sun.tools.javac.tree.JCTree.JCIdent;
72 import com.sun.tools.javac.util.Context;
73 import com.sun.tools.javac.util.Names;
74 
75 import combo.ComboParameter;
76 import combo.ComboTask;
77 import combo.ComboTestHelper;
78 import combo.ComboInstance;
79 import combo.ComboTask.Result;
80 
81 import static com.sun.tools.javac.jvm.ClassFile.*;
82 
83 public class TestInvokeDynamic extends ComboInstance<TestInvokeDynamic> {
84 
85     enum StaticArgumentKind implements ComboParameter {
86         STRING("Hello!", "String", "Ljava/lang/String;") {
87             @Override
check(CPInfo cpInfo)88             boolean check(CPInfo cpInfo) throws Exception {
89                 return (cpInfo instanceof CONSTANT_String_info) &&
90                         ((CONSTANT_String_info)cpInfo).getString()
91                         .equals(value);
92             }
93         },
CLASS(null, R, R)94         CLASS(null, "Class<?>", "Ljava/lang/Class;") {
95             @Override
96             boolean check(CPInfo cpInfo) throws Exception {
97                 return (cpInfo instanceof CONSTANT_Class_info) &&
98                         ((CONSTANT_Class_info)cpInfo).getName()
99                         .equals("java/lang/String");
100             }
101         },
102         INTEGER(1, "int", "I") {
103             @Override
check(CPInfo cpInfo)104             boolean check(CPInfo cpInfo) throws Exception {
105                 return (cpInfo instanceof CONSTANT_Integer_info) &&
106                         ((CONSTANT_Integer_info)cpInfo).value ==
107                         ((Integer)value).intValue();
108             }
109         },
110         LONG(1L, "long", "J") {
111             @Override
check(CPInfo cpInfo)112             boolean check(CPInfo cpInfo) throws Exception {
113                 return (cpInfo instanceof CONSTANT_Long_info) &&
114                         ((CONSTANT_Long_info)cpInfo).value ==
115                         ((Long)value).longValue();
116             }
117         },
118         FLOAT(1.0f, "float", "F") {
119             @Override
check(CPInfo cpInfo)120             boolean check(CPInfo cpInfo) throws Exception {
121                 return (cpInfo instanceof CONSTANT_Float_info) &&
122                         ((CONSTANT_Float_info)cpInfo).value ==
123                         ((Float)value).floatValue();
124             }
125         },
126         DOUBLE(1.0, "double","D") {
127             @Override
check(CPInfo cpInfo)128             boolean check(CPInfo cpInfo) throws Exception {
129                 return (cpInfo instanceof CONSTANT_Double_info) &&
130                         ((CONSTANT_Double_info)cpInfo).value ==
131                         ((Double)value).doubleValue();
132             }
133         },
METHOD_HANDLE(null, R, R)134         METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") {
135             @Override
136             boolean check(CPInfo cpInfo) throws Exception {
137                 if (!(cpInfo instanceof CONSTANT_MethodHandle_info))
138                     return false;
139                 CONSTANT_MethodHandle_info handleInfo =
140                         (CONSTANT_MethodHandle_info)cpInfo;
141                 return handleInfo.getCPRefInfo().getClassName().equals("Array") &&
142                         handleInfo.reference_kind == RefKind.REF_invokeVirtual &&
143                         handleInfo.getCPRefInfo()
144                         .getNameAndTypeInfo().getName().equals("clone") &&
145                         handleInfo.getCPRefInfo()
146                         .getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;");
147             }
148         },
METHOD_TYPE(null, R, R)149         METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") {
150             @Override
151             boolean check(CPInfo cpInfo) throws Exception {
152                 return (cpInfo instanceof CONSTANT_MethodType_info) &&
153                         ((CONSTANT_MethodType_info)cpInfo).getType()
154                         .equals("()Ljava/lang/Object;");
155             }
156         };
157 
158         Object value;
159         String sourceTypeStr;
160         String bytecodeTypeStr;
161 
StaticArgumentKind(Object value, String sourceTypeStr, String bytecodeTypeStr)162         StaticArgumentKind(Object value, String sourceTypeStr,
163                 String bytecodeTypeStr) {
164             this.value = value;
165             this.sourceTypeStr = sourceTypeStr;
166             this.bytecodeTypeStr = bytecodeTypeStr;
167         }
168 
check(CPInfo cpInfo)169         abstract boolean check(CPInfo cpInfo) throws Exception;
170 
getValue(Symtab syms, Names names, Types types)171         Object getValue(Symtab syms, Names names, Types types) {
172             switch (this) {
173                 case STRING:
174                 case INTEGER:
175                 case LONG:
176                 case FLOAT:
177                 case DOUBLE:
178                     return value;
179                 case CLASS:
180                     return syms.stringType.tsym;
181                 case METHOD_HANDLE:
182                     return new Pool.MethodHandle(REF_invokeVirtual,
183                             syms.arrayCloneMethod, types);
184                 case METHOD_TYPE:
185                     return syms.arrayCloneMethod.type;
186                 default:
187                     throw new AssertionError();
188             }
189         }
190 
191         @Override
expand(String optParameter)192         public String expand(String optParameter) {
193             return sourceTypeStr;
194         }
195     }
196 
197     enum StaticArgumentsArity implements ComboParameter {
198         ZERO(0, ""),
199         ONE(1, ",#{SARG[0]} s1"),
200         TWO(2, ",#{SARG[0]} s1, #{SARG[1]} s2"),
201         THREE(3, ",#{SARG[0]} s1, #{SARG[1]} s2, #{SARG[2]} s3");
202 
203         int arity;
204         String argsTemplate;
205 
StaticArgumentsArity(int arity, String argsTemplate)206         StaticArgumentsArity(int arity, String argsTemplate) {
207             this.arity = arity;
208             this.argsTemplate = argsTemplate;
209         }
210 
211         @Override
expand(String optParameter)212         public String expand(String optParameter) {
213             return argsTemplate;
214         }
215     }
216 
main(String... args)217     public static void main(String... args) throws Exception {
218         new ComboTestHelper<TestInvokeDynamic>()
219                 .withFilter(TestInvokeDynamic::redundantTestFilter)
220                 .withDimension("SARGS", (x, arity) -> x.arity = arity, StaticArgumentsArity.values())
221                 .withArrayDimension("SARG", (x, arg, idx) -> x.saks[idx] = arg, 3, StaticArgumentKind.values())
222                 .run(TestInvokeDynamic::new);
223     }
224 
225     StaticArgumentsArity arity;
226     StaticArgumentKind[] saks = new StaticArgumentKind[3];
227 
redundantTestFilter()228     boolean redundantTestFilter() {
229         for (int i = arity.arity ; i < saks.length ; i++) {
230             if (saks[i].ordinal() != 0) {
231                 return false;
232             }
233         }
234         return true;
235     }
236 
237     final String source_template =
238                 "import java.lang.invoke.*;\n" +
239                 "class Test {\n" +
240                 "   void m() { }\n" +
241                 "   void test() {\n" +
242                 "      Object o = this; // marker statement \n" +
243                 "      m();\n" +
244                 "   }\n" +
245                 "}\n" +
246                 "class Bootstrap {\n" +
247                 "   public static CallSite bsm(MethodHandles.Lookup lookup, " +
248                 "String name, MethodType methodType #{SARGS}) {\n" +
249                 "       return null;\n" +
250                 "   }\n" +
251                 "}";
252 
253     @Override
doWork()254     public void doWork() throws IOException {
255         newCompilationTask()
256                 .withOption("-g")
257                 .withSourceFromTemplate(source_template)
258                 .withListenerFactory(context -> {
259                         Symtab syms = Symtab.instance(context);
260                         Names names = Names.instance(context);
261                         Types types = Types.instance(context);
262                         return new Indifier(syms, names, types);
263                     })
264                 .generate(this::verifyBytecode);
265     }
266 
verifyBytecode(Result<Iterable<? extends JavaFileObject>> res)267     void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {
268         if (res.hasErrors()) {
269             fail("Diags found when compiling instance: " + res.compilationInfo());
270             return;
271         }
272         try (InputStream is = res.get().iterator().next().openInputStream()){
273             ClassFile cf = ClassFile.read(is);
274             Method testMethod = null;
275             for (Method m : cf.methods) {
276                 if (m.getName(cf.constant_pool).equals("test")) {
277                     testMethod = m;
278                     break;
279                 }
280             }
281             if (testMethod == null) {
282                 fail("Test method not found");
283                 return;
284             }
285             Code_attribute ea =
286                     (Code_attribute)testMethod.attributes.get(Attribute.Code);
287             if (testMethod == null) {
288                 fail("Code attribute for test() method not found");
289                 return;
290             }
291 
292             int bsmIdx = -1;
293 
294             for (Instruction i : ea.getInstructions()) {
295                 if (i.getMnemonic().equals("invokedynamic")) {
296                     CONSTANT_InvokeDynamic_info indyInfo =
297                          (CONSTANT_InvokeDynamic_info)cf
298                             .constant_pool.get(i.getShort(1));
299                     bsmIdx = indyInfo.bootstrap_method_attr_index;
300                     if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) {
301                         fail("type mismatch for CONSTANT_InvokeDynamic_info");
302                         return;
303                     }
304                 }
305             }
306             if (bsmIdx == -1) {
307                 fail("Missing invokedynamic in generated code");
308                 return;
309             }
310 
311             BootstrapMethods_attribute bsm_attr =
312                     (BootstrapMethods_attribute)cf
313                     .getAttribute(Attribute.BootstrapMethods);
314             if (bsm_attr.bootstrap_method_specifiers.length != 1) {
315                 fail("Bad number of method specifiers " +
316                         "in BootstrapMethods attribute");
317                 return;
318             }
319             BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
320                     bsm_attr.bootstrap_method_specifiers[0];
321 
322             if (bsm_spec.bootstrap_arguments.length != arity.arity) {
323                 fail("Bad number of static invokedynamic args " +
324                         "in BootstrapMethod attribute");
325                 return;
326             }
327 
328             for (int i = 0 ; i < arity.arity ; i++) {
329                 if (!saks[i].check(cf.constant_pool
330                         .get(bsm_spec.bootstrap_arguments[i]))) {
331                     fail("Bad static argument value " + saks[i]);
332                     return;
333                 }
334             }
335 
336             CONSTANT_MethodHandle_info bsm_handle =
337                     (CONSTANT_MethodHandle_info)cf.constant_pool
338                     .get(bsm_spec.bootstrap_method_ref);
339 
340             if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) {
341                 fail("Bad kind on boostrap method handle");
342                 return;
343             }
344 
345             CONSTANT_Methodref_info bsm_ref =
346                     (CONSTANT_Methodref_info)cf.constant_pool
347                     .get(bsm_handle.reference_index);
348 
349             if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) {
350                 fail("Bad owner of boostrap method");
351                 return;
352             }
353 
354             if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) {
355                 fail("Bad boostrap method name");
356                 return;
357             }
358 
359             if (!bsm_ref.getNameAndTypeInfo()
360                     .getType().equals(asBSMSignatureString())) {
361                 fail("Bad boostrap method type" +
362                         bsm_ref.getNameAndTypeInfo().getType() + " " +
363                         asBSMSignatureString());
364                 return;
365             }
366 
367             LineNumberTable_attribute lnt =
368                     (LineNumberTable_attribute)ea.attributes.get(Attribute.LineNumberTable);
369 
370             if (lnt == null) {
371                 fail("No LineNumberTable attribute");
372                 return;
373             }
374             if (lnt.line_number_table_length != 3) {
375                 fail("Wrong number of entries in LineNumberTable");
376                 return;
377             }
378         } catch (Exception e) {
379             e.printStackTrace();
380             fail("error reading classfile: " + res.compilationInfo());
381             return;
382         }
383     }
384 
asBSMSignatureString()385     String asBSMSignatureString() {
386         StringBuilder buf = new StringBuilder();
387         buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;");
388         for (int i = 0 ; i < arity.arity ; i++) {
389             buf.append(saks[i].bytecodeTypeStr);
390         }
391         buf.append(")Ljava/lang/invoke/CallSite;");
392         return buf.toString();
393     }
394 
395     class Indifier extends TreeScanner<Void, Void> implements TaskListener {
396 
397         MethodSymbol bsm;
398         Symtab syms;
399         Names names;
400         Types types;
401 
Indifier(Symtab syms, Names names, Types types)402         Indifier(Symtab syms, Names names, Types types) {
403             this.syms = syms;
404             this.names = names;
405             this.types = types;
406         }
407 
408         @Override
started(TaskEvent e)409         public void started(TaskEvent e) {
410             //do nothing
411         }
412 
413         @Override
finished(TaskEvent e)414         public void finished(TaskEvent e) {
415             if (e.getKind() == TaskEvent.Kind.ANALYZE) {
416                 scan(e.getCompilationUnit(), null);
417             }
418         }
419 
420         @Override
visitMethodInvocation(MethodInvocationTree node, Void p)421         public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
422             super.visitMethodInvocation(node, p);
423             JCMethodInvocation apply = (JCMethodInvocation)node;
424             JCIdent ident = (JCIdent)apply.meth;
425             Symbol oldSym = ident.sym;
426             if (!oldSym.isConstructor()) {
427                 Object[] staticArgs = new Object[arity.arity];
428                 for (int i = 0; i < arity.arity ; i++) {
429                     staticArgs[i] = saks[i].getValue(syms, names, types);
430                 }
431                 ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name,
432                         oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs);
433             }
434             return null;
435         }
436 
437         @Override
visitMethod(MethodTree node, Void p)438         public Void visitMethod(MethodTree node, Void p) {
439             super.visitMethod(node, p);
440             if (node.getName().toString().equals("bsm")) {
441                 bsm = ((JCMethodDecl)node).sym;
442             }
443             return null;
444         }
445     }
446 }
447