1 /*
2  * Copyright (c) 2013, 2020, 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 8129962 8238358
27  * @summary Lambda back-end should generate invokevirtual for method handles referring to
28  *          private instance methods as lambda proxy is a nestmate of the target clsas
29  * @library /tools/javac/lib
30  * @modules jdk.jdeps/com.sun.tools.classfile
31  *          jdk.compiler/com.sun.tools.javac.api
32  *          jdk.compiler/com.sun.tools.javac.file
33  *          jdk.compiler/com.sun.tools.javac.util
34  * @build combo.ComboTestHelper
35  * @run main TestLambdaBytecode
36  */
37 
38 import com.sun.tools.classfile.Attribute;
39 import com.sun.tools.classfile.BootstrapMethods_attribute;
40 import com.sun.tools.classfile.ClassFile;
41 import com.sun.tools.classfile.Code_attribute;
42 import com.sun.tools.classfile.ConstantPool.*;
43 import com.sun.tools.classfile.Instruction;
44 import com.sun.tools.classfile.Method;
45 
46 import java.io.IOException;
47 import java.io.InputStream;
48 
49 import combo.ComboInstance;
50 import combo.ComboParameter;
51 import combo.ComboTask.Result;
52 import combo.ComboTestHelper;
53 
54 import javax.tools.JavaFileObject;
55 
56 public class TestLambdaBytecode extends ComboInstance<TestLambdaBytecode> {
57 
58     static final int MF_ARITY = 3;
59     static final String MH_SIG = "()V";
60 
61     enum ClassKind implements ComboParameter {
62         CLASS("class"),
63         INTERFACE("interface");
64 
65         String classStr;
66 
ClassKind(String classStr)67         ClassKind(String classStr) {
68             this.classStr = classStr;
69         }
70 
71         @Override
expand(String optParameter)72         public String expand(String optParameter) {
73             return classStr;
74         }
75     }
76 
77     enum AccessKind implements ComboParameter {
78         PUBLIC("public"),
79         PRIVATE("private");
80 
81         String accessStr;
82 
AccessKind(String accessStr)83         AccessKind(String accessStr) {
84             this.accessStr = accessStr;
85         }
86 
87         @Override
expand(String optParameter)88         public String expand(String optParameter) {
89             return accessStr;
90         }
91     }
92 
93     enum StaticKind implements ComboParameter {
94         STATIC("static"),
95         INSTANCE("");
96 
97         String staticStr;
98 
StaticKind(String staticStr)99         StaticKind(String staticStr) {
100             this.staticStr = staticStr;
101         }
102 
103         @Override
expand(String optParameter)104         public String expand(String optParameter) {
105             return staticStr;
106         }
107     }
108 
109     enum DefaultKind implements ComboParameter {
110         DEFAULT("default"),
111         NO_DEFAULT("");
112 
113         String defaultStr;
114 
DefaultKind(String defaultStr)115         DefaultKind(String defaultStr) {
116             this.defaultStr = defaultStr;
117         }
118 
119         @Override
expand(String optParameter)120         public String expand(String optParameter) {
121             return defaultStr;
122         }
123     }
124 
125     static class MethodKind {
126         ClassKind ck;
127         AccessKind ak;
128         StaticKind sk;
129         DefaultKind dk;
130 
MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk)131         MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) {
132             this.ck = ck;
133             this.ak = ak;
134             this.sk = sk;
135             this.dk = dk;
136         }
137 
inInterface()138         boolean inInterface() {
139             return ck == ClassKind.INTERFACE;
140         }
141 
isPrivate()142         boolean isPrivate() {
143             return ak == AccessKind.PRIVATE;
144         }
145 
isStatic()146         boolean isStatic() {
147             return sk == StaticKind.STATIC;
148         }
149 
isDefault()150         boolean isDefault() {
151             return dk == DefaultKind.DEFAULT;
152         }
153 
isOK()154         boolean isOK() {
155             if (isDefault() && (!inInterface() || isStatic())) {
156                 return false;
157             } else if (inInterface() &&
158                     ((!isStatic() && !isDefault()) || isPrivate())) {
159                 return false;
160             } else {
161                 return true;
162             }
163         }
164     }
165 
main(String... args)166     public static void main(String... args) throws Exception {
167         new ComboTestHelper<TestLambdaBytecode>()
168                 .withDimension("CLASSKIND", (x, ck) -> x.ck = ck, ClassKind.values())
169                 .withArrayDimension("ACCESS", (x, acc, idx) -> x.accessKinds[idx] = acc, 2, AccessKind.values())
170                 .withArrayDimension("STATIC", (x, sk, idx) -> x.staticKinds[idx] = sk, 2, StaticKind.values())
171                 .withArrayDimension("DEFAULT", (x, dk, idx) -> x.defaultKinds[idx] = dk, 2, DefaultKind.values())
172                 .run(TestLambdaBytecode::new, TestLambdaBytecode::init);
173     }
174 
175     ClassKind ck;
176     AccessKind[] accessKinds = new AccessKind[2];
177     StaticKind[] staticKinds = new StaticKind[2];
178     DefaultKind[] defaultKinds = new DefaultKind[2];
179     MethodKind mk1, mk2;
180 
init()181     void init() {
182         mk1 = new MethodKind(ck, accessKinds[0], staticKinds[0], defaultKinds[0]);
183         mk2 = new MethodKind(ck, accessKinds[1], staticKinds[1], defaultKinds[1]);
184     }
185 
186     String source_template =
187                 "#{CLASSKIND} Test {\n" +
188                 "   #{ACCESS[0]} #{STATIC[0]} #{DEFAULT[0]} void test() { Runnable r = ()->{ target(); }; }\n" +
189                 "   #{ACCESS[1]} #{STATIC[1]} #{DEFAULT[1]} void target() { }\n" +
190                 "}\n";
191 
192     @Override
doWork()193     public void doWork() throws IOException {
194         newCompilationTask()
195                 .withSourceFromTemplate(source_template)
196                 .generate(this::verifyBytecode);
197     }
198 
verifyBytecode(Result<Iterable<? extends JavaFileObject>> res)199     void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {
200         if (res.hasErrors()) {
201             boolean errorExpected = !mk1.isOK() || !mk2.isOK();
202             errorExpected |= mk1.isStatic() && !mk2.isStatic();
203 
204             if (!errorExpected) {
205                 fail("Diags found when compiling instance; " + res.compilationInfo());
206             }
207             return;
208         }
209         try (InputStream is = res.get().iterator().next().openInputStream()) {
210             ClassFile cf = ClassFile.read(is);
211             Method testMethod = null;
212             for (Method m : cf.methods) {
213                 if (m.getName(cf.constant_pool).equals("test")) {
214                     testMethod = m;
215                     break;
216                 }
217             }
218             if (testMethod == null) {
219                 fail("Test method not found");
220                 return;
221             }
222             Code_attribute ea =
223                     (Code_attribute)testMethod.attributes.get(Attribute.Code);
224             if (testMethod == null) {
225                 fail("Code attribute for test() method not found");
226                 return;
227             }
228 
229             int bsmIdx = -1;
230 
231             for (Instruction i : ea.getInstructions()) {
232                 if (i.getMnemonic().equals("invokedynamic")) {
233                     CONSTANT_InvokeDynamic_info indyInfo =
234                          (CONSTANT_InvokeDynamic_info)cf
235                             .constant_pool.get(i.getShort(1));
236                     bsmIdx = indyInfo.bootstrap_method_attr_index;
237                     if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType())) {
238                         fail("type mismatch for CONSTANT_InvokeDynamic_info " +
239                                 res.compilationInfo() + "\n" + indyInfo.getNameAndTypeInfo().getType() +
240                                 "\n" + makeIndyType());
241                         return;
242                     }
243                 }
244             }
245             if (bsmIdx == -1) {
246                 fail("Missing invokedynamic in generated code");
247                 return;
248             }
249 
250             BootstrapMethods_attribute bsm_attr =
251                     (BootstrapMethods_attribute)cf
252                     .getAttribute(Attribute.BootstrapMethods);
253             if (bsm_attr.bootstrap_method_specifiers.length != 1) {
254                 fail("Bad number of method specifiers " +
255                         "in BootstrapMethods attribute");
256                 return;
257             }
258             BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
259                     bsm_attr.bootstrap_method_specifiers[0];
260 
261             if (bsm_spec.bootstrap_arguments.length != MF_ARITY) {
262                 fail("Bad number of static invokedynamic args " +
263                         "in BootstrapMethod attribute");
264                 return;
265             }
266 
267             CONSTANT_MethodHandle_info mh =
268                     (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]);
269 
270             boolean kindOK;
271             switch (mh.reference_kind) {
272                 case REF_invokeStatic: kindOK = mk2.isStatic(); break;
273                 case REF_invokeVirtual: kindOK = !mk2.isStatic() && !mk2.inInterface(); break;
274                 case REF_invokeInterface: kindOK = mk2.inInterface(); break;
275                 default:
276                     kindOK = false;
277             }
278 
279             if (!kindOK) {
280                 fail("Bad invoke kind in implementation method handle: " + mh.reference_kind);
281                 return;
282             }
283 
284             if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) {
285                 fail("Type mismatch in implementation method handle");
286                 return;
287             }
288         } catch (Exception e) {
289             e.printStackTrace();
290             fail("error reading " + res.compilationInfo() + ": " + e);
291         }
292     }
293 
makeIndyType()294     String makeIndyType() {
295         StringBuilder buf = new StringBuilder();
296         buf.append("(");
297         if (!mk2.isStatic()) {
298             buf.append("LTest;");
299         }
300         buf.append(")Ljava/lang/Runnable;");
301         return buf.toString();
302     }
303 }
304