1 /*
  2  * Copyright (c) 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 8238358
 27  * @summary Lambda back-end should generate invokespecial for method handles referring to
 28  *          private instance methods when compiling with --release 14
 29  * @enablePreview
 30  * @library /tools/javac/lib
 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 TestLambdaBytecodeTargetRelease14
 37  */
 38 
 39 import java.lang.classfile.*;
 40 import java.lang.classfile.attribute.*;
 41 import java.lang.classfile.constantpool.InvokeDynamicEntry;
 42 import java.lang.classfile.constantpool.MethodHandleEntry;
 43 import java.lang.classfile.instruction.InvokeDynamicInstruction;
 44 
 45 import java.io.IOException;
 46 import java.io.InputStream;
 47 
 48 import combo.ComboInstance;
 49 import combo.ComboParameter;
 50 import combo.ComboTask.Result;
 51 import combo.ComboTestHelper;
 52 
 53 import java.lang.invoke.MethodHandleInfo;
 54 import javax.tools.JavaFileObject;
 55 
 56 public class TestLambdaBytecodeTargetRelease14 extends ComboInstance<TestLambdaBytecodeTargetRelease14> {
 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 
 67         ClassKind(String classStr) {
 68             this.classStr = classStr;
 69         }
 70 
 71         @Override
 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 
 83         AccessKind(String accessStr) {
 84             this.accessStr = accessStr;
 85         }
 86 
 87         @Override
 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 
 99         StaticKind(String staticStr) {
100             this.staticStr = staticStr;
101         }
102 
103         @Override
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 
115         DefaultKind(String defaultStr) {
116             this.defaultStr = defaultStr;
117         }
118 
119         @Override
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 
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 
138         boolean inInterface() {
139             return ck == ClassKind.INTERFACE;
140         }
141 
142         boolean isPrivate() {
143             return ak == AccessKind.PRIVATE;
144         }
145 
146         boolean isStatic() {
147             return sk == StaticKind.STATIC;
148         }
149 
150         boolean isDefault() {
151             return dk == DefaultKind.DEFAULT;
152         }
153 
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 
166     public static void main(String... args) throws Exception {
167         new ComboTestHelper<TestLambdaBytecodeTargetRelease14>()
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(TestLambdaBytecodeTargetRelease14::new, TestLambdaBytecodeTargetRelease14::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 
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
193     public void doWork() throws IOException {
194         newCompilationTask()
195                 .withSourceFromTemplate(source_template)
196                 .withOption("--release").withOption("14")
197                 .generate(this::verifyBytecode);
198     }
199 
200     void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {
201         if (res.hasErrors()) {
202             boolean errorExpected = !mk1.isOK() || !mk2.isOK();
203             errorExpected |= mk1.isStatic() && !mk2.isStatic();
204 
205             if (!errorExpected) {
206                 fail("Diags found when compiling instance; " + res.compilationInfo());
207             }
208             return;
209         }
210         try (InputStream is = res.get().iterator().next().openInputStream()) {
211             ClassModel cm = ClassFile.of().parse(is.readAllBytes());
212             MethodModel testMethod = null;
213             for (MethodModel m : cm.methods()) {
214                 if (m.methodName().equalsString("test")) {
215                     testMethod = m;
216                     break;
217                 }
218             }
219             if (testMethod == null) {
220                 fail("Test method not found");
221                 return;
222             }
223             CodeAttribute ea = testMethod.findAttribute(Attributes.code()).orElse(null);
224             if (ea == null) {
225                 fail("Code attribute for test() method not found");
226                 return;
227             }
228 
229             int bsmIdx = -1;
230 
231             for (CodeElement ce : ea.elementList()) {
232                 if (ce instanceof InvokeDynamicInstruction indy) {
233                     InvokeDynamicEntry indyInfo = indy.invokedynamic();
234                     bsmIdx = indyInfo.bootstrap().bsmIndex();
235                     if (!indyInfo.type().equalsString(makeIndyType())) {
236                         fail("type mismatch for CONSTANT_InvokeDynamic_info " +
237                                 res.compilationInfo() + "\n" + indyInfo.type().stringValue() +
238                                 "\n" + makeIndyType());
239                         return;
240                     }
241                 }
242             }
243             if (bsmIdx == -1) {
244                 fail("Missing invokedynamic in generated code");
245                 return;
246             }
247 
248             BootstrapMethodsAttribute bsm_attr = cm.findAttribute(Attributes.bootstrapMethods()).orElseThrow();
249             if (bsm_attr.bootstrapMethodsSize() != 1) {
250                 fail("Bad number of method specifiers " +
251                         "in BootstrapMethods attribute");
252                 return;
253             }
254             BootstrapMethodEntry bsm_spec = bsm_attr.bootstrapMethods().get(0);
255 
256             if (bsm_spec.arguments().size() != MF_ARITY) {
257                 fail("Bad number of static invokedynamic args " +
258                         "in BootstrapMethod attribute");
259                 return;
260             }
261 
262             MethodHandleEntry mh = (MethodHandleEntry) bsm_spec.arguments().get(1);
263 
264             boolean kindOK = switch (mh.kind()) {
265                 case MethodHandleInfo.REF_invokeStatic -> mk2.isStatic();
266                 case MethodHandleInfo.REF_invokeSpecial -> !mk2.isStatic();
267                 case MethodHandleInfo.REF_invokeInterface -> mk2.inInterface();
268                 default -> false;
269             };
270 
271             if (!kindOK) {
272                 fail("Bad invoke kind in implementation method handle");
273                 return;
274             }
275 
276             if (!mh.reference().type().equalsString(MH_SIG)) {
277                 fail("Type mismatch in implementation method handle");
278             }
279         } catch (Exception e) {
280             e.printStackTrace();
281             fail("error reading " + res.compilationInfo() + ": " + e);
282         }
283     }
284 
285     String makeIndyType() {
286         StringBuilder buf = new StringBuilder();
287         buf.append("(");
288         if (!mk2.isStatic()) {
289             buf.append("LTest;");
290         }
291         buf.append(")Ljava/lang/Runnable;");
292         return buf.toString();
293     }
294 }