TestInvokeDynamic.java revision 3019:176472b94f2e
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
43import java.io.IOException;
44import java.io.InputStream;
45
46import javax.tools.JavaFileObject;
47
48import com.sun.source.tree.MethodInvocationTree;
49import com.sun.source.tree.MethodTree;
50import com.sun.source.util.TaskEvent;
51import com.sun.source.util.TaskListener;
52import com.sun.source.util.TreeScanner;
53
54import com.sun.tools.classfile.Attribute;
55import com.sun.tools.classfile.BootstrapMethods_attribute;
56import com.sun.tools.classfile.ClassFile;
57import com.sun.tools.classfile.Code_attribute;
58import com.sun.tools.classfile.ConstantPool.*;
59import com.sun.tools.classfile.Instruction;
60import com.sun.tools.classfile.LineNumberTable_attribute;
61import com.sun.tools.classfile.Method;
62
63import com.sun.tools.javac.api.JavacTaskImpl;
64import com.sun.tools.javac.code.Symbol;
65import com.sun.tools.javac.code.Symbol.MethodSymbol;
66import com.sun.tools.javac.code.Symtab;
67import com.sun.tools.javac.code.Types;
68import com.sun.tools.javac.jvm.Pool;
69import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
70import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
71import com.sun.tools.javac.tree.JCTree.JCIdent;
72import com.sun.tools.javac.util.Context;
73import com.sun.tools.javac.util.Names;
74
75import combo.ComboParameter;
76import combo.ComboTask;
77import combo.ComboTestHelper;
78import combo.ComboInstance;
79import combo.ComboTask.Result;
80
81import static com.sun.tools.javac.jvm.ClassFile.*;
82
83public class TestInvokeDynamic extends ComboInstance<TestInvokeDynamic> {
84
85    enum StaticArgumentKind implements ComboParameter {
86        STRING("Hello!", "String", "Ljava/lang/String;") {
87            @Override
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        },
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
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
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
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
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        },
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        },
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
162        StaticArgumentKind(Object value, String sourceTypeStr,
163                String bytecodeTypeStr) {
164            this.value = value;
165            this.sourceTypeStr = sourceTypeStr;
166            this.bytecodeTypeStr = bytecodeTypeStr;
167        }
168
169        abstract boolean check(CPInfo cpInfo) throws Exception;
170
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
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
206        StaticArgumentsArity(int arity, String argsTemplate) {
207            this.arity = arity;
208            this.argsTemplate = argsTemplate;
209        }
210
211        @Override
212        public String expand(String optParameter) {
213            return argsTemplate;
214        }
215    }
216
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
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
254    public void doWork() throws IOException {
255        ComboTask comboTask = newCompilationTask()
256                .withOption("-g")
257                .withSourceFromTemplate(source_template);
258
259        JavacTaskImpl ct = (JavacTaskImpl)comboTask.getTask();
260        Context context = ct.getContext();
261        Symtab syms = Symtab.instance(context);
262        Names names = Names.instance(context);
263        Types types = Types.instance(context);
264        ct.addTaskListener(new Indifier(syms, names, types));
265        verifyBytecode(comboTask.generate());
266    }
267
268    void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {
269        if (res.hasErrors()) {
270            fail("Diags found when compiling instance: " + res.compilationInfo());
271            return;
272        }
273        try (InputStream is = res.get().iterator().next().openInputStream()){
274            ClassFile cf = ClassFile.read(is);
275            Method testMethod = null;
276            for (Method m : cf.methods) {
277                if (m.getName(cf.constant_pool).equals("test")) {
278                    testMethod = m;
279                    break;
280                }
281            }
282            if (testMethod == null) {
283                fail("Test method not found");
284                return;
285            }
286            Code_attribute ea =
287                    (Code_attribute)testMethod.attributes.get(Attribute.Code);
288            if (testMethod == null) {
289                fail("Code attribute for test() method not found");
290                return;
291            }
292
293            int bsmIdx = -1;
294
295            for (Instruction i : ea.getInstructions()) {
296                if (i.getMnemonic().equals("invokedynamic")) {
297                    CONSTANT_InvokeDynamic_info indyInfo =
298                         (CONSTANT_InvokeDynamic_info)cf
299                            .constant_pool.get(i.getShort(1));
300                    bsmIdx = indyInfo.bootstrap_method_attr_index;
301                    if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) {
302                        fail("type mismatch for CONSTANT_InvokeDynamic_info");
303                        return;
304                    }
305                }
306            }
307            if (bsmIdx == -1) {
308                fail("Missing invokedynamic in generated code");
309                return;
310            }
311
312            BootstrapMethods_attribute bsm_attr =
313                    (BootstrapMethods_attribute)cf
314                    .getAttribute(Attribute.BootstrapMethods);
315            if (bsm_attr.bootstrap_method_specifiers.length != 1) {
316                fail("Bad number of method specifiers " +
317                        "in BootstrapMethods attribute");
318                return;
319            }
320            BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
321                    bsm_attr.bootstrap_method_specifiers[0];
322
323            if (bsm_spec.bootstrap_arguments.length != arity.arity) {
324                fail("Bad number of static invokedynamic args " +
325                        "in BootstrapMethod attribute");
326                return;
327            }
328
329            for (int i = 0 ; i < arity.arity ; i++) {
330                if (!saks[i].check(cf.constant_pool
331                        .get(bsm_spec.bootstrap_arguments[i]))) {
332                    fail("Bad static argument value " + saks[i]);
333                    return;
334                }
335            }
336
337            CONSTANT_MethodHandle_info bsm_handle =
338                    (CONSTANT_MethodHandle_info)cf.constant_pool
339                    .get(bsm_spec.bootstrap_method_ref);
340
341            if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) {
342                fail("Bad kind on boostrap method handle");
343                return;
344            }
345
346            CONSTANT_Methodref_info bsm_ref =
347                    (CONSTANT_Methodref_info)cf.constant_pool
348                    .get(bsm_handle.reference_index);
349
350            if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) {
351                fail("Bad owner of boostrap method");
352                return;
353            }
354
355            if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) {
356                fail("Bad boostrap method name");
357                return;
358            }
359
360            if (!bsm_ref.getNameAndTypeInfo()
361                    .getType().equals(asBSMSignatureString())) {
362                fail("Bad boostrap method type" +
363                        bsm_ref.getNameAndTypeInfo().getType() + " " +
364                        asBSMSignatureString());
365                return;
366            }
367
368            LineNumberTable_attribute lnt =
369                    (LineNumberTable_attribute)ea.attributes.get(Attribute.LineNumberTable);
370
371            if (lnt == null) {
372                fail("No LineNumberTable attribute");
373                return;
374            }
375            if (lnt.line_number_table_length != 3) {
376                fail("Wrong number of entries in LineNumberTable");
377                return;
378            }
379        } catch (Exception e) {
380            e.printStackTrace();
381            fail("error reading classfile: " + res.compilationInfo());
382            return;
383        }
384    }
385
386    String asBSMSignatureString() {
387        StringBuilder buf = new StringBuilder();
388        buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;");
389        for (int i = 0 ; i < arity.arity ; i++) {
390            buf.append(saks[i].bytecodeTypeStr);
391        }
392        buf.append(")Ljava/lang/invoke/CallSite;");
393        return buf.toString();
394    }
395
396    class Indifier extends TreeScanner<Void, Void> implements TaskListener {
397
398        MethodSymbol bsm;
399        Symtab syms;
400        Names names;
401        Types types;
402
403        Indifier(Symtab syms, Names names, Types types) {
404            this.syms = syms;
405            this.names = names;
406            this.types = types;
407        }
408
409        @Override
410        public void started(TaskEvent e) {
411            //do nothing
412        }
413
414        @Override
415        public void finished(TaskEvent e) {
416            if (e.getKind() == TaskEvent.Kind.ANALYZE) {
417                scan(e.getCompilationUnit(), null);
418            }
419        }
420
421        @Override
422        public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
423            super.visitMethodInvocation(node, p);
424            JCMethodInvocation apply = (JCMethodInvocation)node;
425            JCIdent ident = (JCIdent)apply.meth;
426            Symbol oldSym = ident.sym;
427            if (!oldSym.isConstructor()) {
428                Object[] staticArgs = new Object[arity.arity];
429                for (int i = 0; i < arity.arity ; i++) {
430                    staticArgs[i] = saks[i].getValue(syms, names, types);
431                }
432                ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name,
433                        oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs);
434            }
435            return null;
436        }
437
438        @Override
439        public Void visitMethod(MethodTree node, Void p) {
440            super.visitMethod(node, p);
441            if (node.getName().toString().equals("bsm")) {
442                bsm = ((JCMethodDecl)node).sym;
443            }
444            return null;
445        }
446    }
447}
448