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