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