21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 |
23 |
24 /* |
24 /* |
25 * @test |
25 * @test |
26 * @bug 8009649 |
26 * @bug 8009649 8129962 |
27 * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods |
27 * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods |
28 * @library ../../lib |
28 * @library /tools/javac/lib |
29 * @modules jdk.jdeps/com.sun.tools.classfile |
29 * @modules jdk.jdeps/com.sun.tools.classfile |
30 * jdk.compiler/com.sun.tools.javac.api |
30 * jdk.compiler/com.sun.tools.javac.api |
31 * @build JavacTestingAbstractThreadedTest |
31 * jdk.compiler/com.sun.tools.javac.code |
32 * @run main/othervm TestLambdaBytecode |
32 * jdk.compiler/com.sun.tools.javac.comp |
|
33 * jdk.compiler/com.sun.tools.javac.main |
|
34 * jdk.compiler/com.sun.tools.javac.tree |
|
35 * jdk.compiler/com.sun.tools.javac.util |
|
36 * @build combo.ComboTestHelper |
|
37 * @run main TestLambdaBytecode |
33 */ |
38 */ |
34 |
|
35 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047) |
|
36 // see JDK-8006746 |
|
37 |
39 |
38 import com.sun.tools.classfile.Attribute; |
40 import com.sun.tools.classfile.Attribute; |
39 import com.sun.tools.classfile.BootstrapMethods_attribute; |
41 import com.sun.tools.classfile.BootstrapMethods_attribute; |
40 import com.sun.tools.classfile.ClassFile; |
42 import com.sun.tools.classfile.ClassFile; |
41 import com.sun.tools.classfile.Code_attribute; |
43 import com.sun.tools.classfile.Code_attribute; |
42 import com.sun.tools.classfile.ConstantPool.*; |
44 import com.sun.tools.classfile.ConstantPool.*; |
43 import com.sun.tools.classfile.Instruction; |
45 import com.sun.tools.classfile.Instruction; |
44 import com.sun.tools.classfile.Method; |
46 import com.sun.tools.classfile.Method; |
45 |
47 |
46 import com.sun.tools.javac.api.JavacTaskImpl; |
48 import java.io.IOException; |
47 |
49 import java.io.InputStream; |
48 |
50 |
49 import java.io.File; |
51 import combo.ComboInstance; |
50 import java.net.URI; |
52 import combo.ComboParameter; |
51 import java.util.ArrayList; |
53 import combo.ComboTask.Result; |
52 import java.util.Arrays; |
54 import combo.ComboTestHelper; |
53 import java.util.Locale; |
55 |
54 |
|
55 import javax.tools.Diagnostic; |
|
56 import javax.tools.JavaFileObject; |
56 import javax.tools.JavaFileObject; |
57 import javax.tools.SimpleJavaFileObject; |
57 |
58 |
58 public class TestLambdaBytecode extends ComboInstance<TestLambdaBytecode> { |
59 import static com.sun.tools.javac.jvm.ClassFile.*; |
59 |
60 |
60 static final int MF_ARITY = 3; |
61 public class TestLambdaBytecode |
61 static final String MH_SIG = "()V"; |
62 extends JavacTestingAbstractThreadedTest |
62 |
63 implements Runnable { |
63 enum ClassKind implements ComboParameter { |
64 |
|
65 enum ClassKind { |
|
66 CLASS("class"), |
64 CLASS("class"), |
67 INTERFACE("interface"); |
65 INTERFACE("interface"); |
68 |
66 |
69 String classStr; |
67 String classStr; |
70 |
68 |
71 ClassKind(String classStr) { |
69 ClassKind(String classStr) { |
72 this.classStr = classStr; |
70 this.classStr = classStr; |
73 } |
71 } |
74 } |
72 |
75 |
73 @Override |
76 enum AccessKind { |
74 public String expand(String optParameter) { |
|
75 return classStr; |
|
76 } |
|
77 } |
|
78 |
|
79 enum AccessKind implements ComboParameter { |
77 PUBLIC("public"), |
80 PUBLIC("public"), |
78 PRIVATE("private"); |
81 PRIVATE("private"); |
79 |
82 |
80 String accessStr; |
83 String accessStr; |
81 |
84 |
82 AccessKind(String accessStr) { |
85 AccessKind(String accessStr) { |
83 this.accessStr = accessStr; |
86 this.accessStr = accessStr; |
84 } |
87 } |
85 } |
88 |
86 |
89 @Override |
87 enum StaticKind { |
90 public String expand(String optParameter) { |
|
91 return accessStr; |
|
92 } |
|
93 } |
|
94 |
|
95 enum StaticKind implements ComboParameter { |
88 STATIC("static"), |
96 STATIC("static"), |
89 INSTANCE(""); |
97 INSTANCE(""); |
90 |
98 |
91 String staticStr; |
99 String staticStr; |
92 |
100 |
93 StaticKind(String staticStr) { |
101 StaticKind(String staticStr) { |
94 this.staticStr = staticStr; |
102 this.staticStr = staticStr; |
95 } |
103 } |
96 } |
104 |
97 |
105 @Override |
98 enum DefaultKind { |
106 public String expand(String optParameter) { |
|
107 return staticStr; |
|
108 } |
|
109 } |
|
110 |
|
111 enum DefaultKind implements ComboParameter { |
99 DEFAULT("default"), |
112 DEFAULT("default"), |
100 NO_DEFAULT(""); |
113 NO_DEFAULT(""); |
101 |
114 |
102 String defaultStr; |
115 String defaultStr; |
103 |
116 |
104 DefaultKind(String defaultStr) { |
117 DefaultKind(String defaultStr) { |
105 this.defaultStr = defaultStr; |
118 this.defaultStr = defaultStr; |
106 } |
119 } |
107 } |
120 |
108 |
121 @Override |
109 enum ExprKind { |
122 public String expand(String optParameter) { |
110 LAMBDA("Runnable r = ()->{ target(); };"); |
123 return defaultStr; |
111 |
|
112 String exprString; |
|
113 |
|
114 ExprKind(String exprString) { |
|
115 this.exprString = exprString; |
|
116 } |
124 } |
117 } |
125 } |
118 |
126 |
119 static class MethodKind { |
127 static class MethodKind { |
120 ClassKind ck; |
128 ClassKind ck; |
153 return false; |
161 return false; |
154 } else { |
162 } else { |
155 return true; |
163 return true; |
156 } |
164 } |
157 } |
165 } |
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 } |
166 } |
169 |
167 |
170 public static void main(String... args) throws Exception { |
168 public static void main(String... args) throws Exception { |
171 for (ClassKind ck : ClassKind.values()) { |
169 new ComboTestHelper<TestLambdaBytecode>() |
172 for (AccessKind ak1 : AccessKind.values()) { |
170 .withDimension("CLASSKIND", (x, ck) -> x.ck = ck, ClassKind.values()) |
173 for (StaticKind sk1 : StaticKind.values()) { |
171 .withArrayDimension("ACCESS", (x, acc, idx) -> x.accessKinds[idx] = acc, 2, AccessKind.values()) |
174 for (DefaultKind dk1 : DefaultKind.values()) { |
172 .withArrayDimension("STATIC", (x, sk, idx) -> x.staticKinds[idx] = sk, 2, StaticKind.values()) |
175 for (AccessKind ak2 : AccessKind.values()) { |
173 .withArrayDimension("DEFAULT", (x, dk, idx) -> x.defaultKinds[idx] = dk, 2, DefaultKind.values()) |
176 for (StaticKind sk2 : StaticKind.values()) { |
174 .run(TestLambdaBytecode::new, TestLambdaBytecode::init); |
177 for (DefaultKind dk2 : DefaultKind.values()) { |
175 } |
178 for (ExprKind ek : ExprKind.values()) { |
176 |
179 pool.execute(new TestLambdaBytecode(ck, ak1, ak2, sk1, sk2, dk1, dk2, ek)); |
177 ClassKind ck; |
180 } |
178 AccessKind[] accessKinds = new AccessKind[2]; |
181 } |
179 StaticKind[] staticKinds = new StaticKind[2]; |
182 } |
180 DefaultKind[] defaultKinds = new DefaultKind[2]; |
183 } |
|
184 } |
|
185 } |
|
186 } |
|
187 } |
|
188 |
|
189 checkAfterExec(); |
|
190 } |
|
191 |
|
192 MethodKind mk1, mk2; |
181 MethodKind mk1, mk2; |
193 ExprKind ek; |
182 |
194 DiagChecker dc; |
183 void init() { |
195 |
184 mk1 = new MethodKind(ck, accessKinds[0], staticKinds[0], defaultKinds[0]); |
196 TestLambdaBytecode(ClassKind ck, AccessKind ak1, AccessKind ak2, StaticKind sk1, |
185 mk2 = new MethodKind(ck, accessKinds[1], staticKinds[1], defaultKinds[1]); |
197 StaticKind sk2, DefaultKind dk1, DefaultKind dk2, ExprKind ek) { |
186 } |
198 mk1 = new MethodKind(ck, ak1, sk1, dk1); |
187 |
199 mk2 = new MethodKind(ck, ak2, sk2, dk2); |
188 String source_template = |
200 this.ek = ek; |
189 "#{CLASSKIND} Test {\n" + |
201 dc = new DiagChecker(); |
190 " #{ACCESS[0]} #{STATIC[0]} #{DEFAULT[0]} void test() { Runnable r = ()->{ target(); }; }\n" + |
202 } |
191 " #{ACCESS[1]} #{STATIC[1]} #{DEFAULT[1]} void target() { }\n" + |
203 |
192 "}\n"; |
204 public void run() { |
193 |
205 int id = checkCount.incrementAndGet(); |
194 @Override |
206 JavaSource source = new JavaSource(id); |
195 public void doWork() throws IOException { |
207 JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc, |
196 verifyBytecode(newCompilationTask() |
208 null, null, Arrays.asList(source)); |
197 .withSourceFromTemplate(source_template) |
209 try { |
198 .generate()); |
210 ct.generate(); |
199 } |
211 } catch (Throwable t) { |
200 |
212 t.printStackTrace(); |
201 void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) { |
213 throw new AssertionError( |
202 if (res.hasErrors()) { |
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(); |
203 boolean errorExpected = !mk1.isOK() || !mk2.isOK(); |
219 errorExpected |= mk1.isStatic() && !mk2.isStatic(); |
204 errorExpected |= mk1.isStatic() && !mk2.isStatic(); |
220 |
205 |
221 if (!errorExpected) { |
206 if (!errorExpected) { |
222 throw new AssertionError( |
207 fail("Diags found when compiling instance; " + res.compilationInfo()); |
223 String.format("Diags found when compiling following code\n%s\n\n%s", |
|
224 source.source, dc.printDiags())); |
|
225 } |
208 } |
226 return; |
209 return; |
227 } |
210 } |
228 verifyBytecode(id, source); |
211 try (InputStream is = res.get().iterator().next().openInputStream()) { |
229 } |
212 ClassFile cf = ClassFile.read(is); |
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; |
213 Method testMethod = null; |
236 for (Method m : cf.methods) { |
214 for (Method m : cf.methods) { |
237 if (m.getName(cf.constant_pool).equals("test")) { |
215 if (m.getName(cf.constant_pool).equals("test")) { |
238 testMethod = m; |
216 testMethod = m; |
239 break; |
217 break; |
240 } |
218 } |
241 } |
219 } |
242 if (testMethod == null) { |
220 if (testMethod == null) { |
243 throw new Error("Test method not found"); |
221 fail("Test method not found"); |
|
222 return; |
244 } |
223 } |
245 Code_attribute ea = |
224 Code_attribute ea = |
246 (Code_attribute)testMethod.attributes.get(Attribute.Code); |
225 (Code_attribute)testMethod.attributes.get(Attribute.Code); |
247 if (testMethod == null) { |
226 if (testMethod == null) { |
248 throw new Error("Code attribute for test() method not found"); |
227 fail("Code attribute for test() method not found"); |
|
228 return; |
249 } |
229 } |
250 |
230 |
251 int bsmIdx = -1; |
231 int bsmIdx = -1; |
252 |
232 |
253 for (Instruction i : ea.getInstructions()) { |
233 for (Instruction i : ea.getInstructions()) { |
254 if (i.getMnemonic().equals("invokedynamic")) { |
234 if (i.getMnemonic().equals("invokedynamic")) { |
255 CONSTANT_InvokeDynamic_info indyInfo = |
235 CONSTANT_InvokeDynamic_info indyInfo = |
256 (CONSTANT_InvokeDynamic_info)cf |
236 (CONSTANT_InvokeDynamic_info)cf |
257 .constant_pool.get(i.getShort(1)); |
237 .constant_pool.get(i.getShort(1)); |
258 bsmIdx = indyInfo.bootstrap_method_attr_index; |
238 bsmIdx = indyInfo.bootstrap_method_attr_index; |
259 if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType(id))) { |
239 if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType())) { |
260 throw new |
240 fail("type mismatch for CONSTANT_InvokeDynamic_info " + |
261 AssertionError("type mismatch for CONSTANT_InvokeDynamic_info " + source.source + "\n" + indyInfo.getNameAndTypeInfo().getType() + "\n" + makeIndyType(id)); |
241 res.compilationInfo() + "\n" + indyInfo.getNameAndTypeInfo().getType() + |
|
242 "\n" + makeIndyType()); |
|
243 return; |
262 } |
244 } |
263 } |
245 } |
264 } |
246 } |
265 if (bsmIdx == -1) { |
247 if (bsmIdx == -1) { |
266 throw new Error("Missing invokedynamic in generated code"); |
248 fail("Missing invokedynamic in generated code"); |
|
249 return; |
267 } |
250 } |
268 |
251 |
269 BootstrapMethods_attribute bsm_attr = |
252 BootstrapMethods_attribute bsm_attr = |
270 (BootstrapMethods_attribute)cf |
253 (BootstrapMethods_attribute)cf |
271 .getAttribute(Attribute.BootstrapMethods); |
254 .getAttribute(Attribute.BootstrapMethods); |
272 if (bsm_attr.bootstrap_method_specifiers.length != 1) { |
255 if (bsm_attr.bootstrap_method_specifiers.length != 1) { |
273 throw new Error("Bad number of method specifiers " + |
256 fail("Bad number of method specifiers " + |
274 "in BootstrapMethods attribute"); |
257 "in BootstrapMethods attribute"); |
|
258 return; |
275 } |
259 } |
276 BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = |
260 BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec = |
277 bsm_attr.bootstrap_method_specifiers[0]; |
261 bsm_attr.bootstrap_method_specifiers[0]; |
278 |
262 |
279 if (bsm_spec.bootstrap_arguments.length != MF_ARITY) { |
263 if (bsm_spec.bootstrap_arguments.length != MF_ARITY) { |
280 throw new Error("Bad number of static invokedynamic args " + |
264 fail("Bad number of static invokedynamic args " + |
281 "in BootstrapMethod attribute"); |
265 "in BootstrapMethod attribute"); |
|
266 return; |
282 } |
267 } |
283 |
268 |
284 CONSTANT_MethodHandle_info mh = |
269 CONSTANT_MethodHandle_info mh = |
285 (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]); |
270 (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]); |
286 |
271 |
292 default: |
277 default: |
293 kindOK = false; |
278 kindOK = false; |
294 } |
279 } |
295 |
280 |
296 if (!kindOK) { |
281 if (!kindOK) { |
297 throw new Error("Bad invoke kind in implementation method handle"); |
282 fail("Bad invoke kind in implementation method handle"); |
|
283 return; |
298 } |
284 } |
299 |
285 |
300 if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) { |
286 if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) { |
301 throw new Error("Type mismatch in implementation method handle"); |
287 fail("Type mismatch in implementation method handle"); |
|
288 return; |
302 } |
289 } |
303 } catch (Exception e) { |
290 } catch (Exception e) { |
304 e.printStackTrace(); |
291 e.printStackTrace(); |
305 throw new Error("error reading " + compiledTest +": " + e); |
292 fail("error reading " + res.compilationInfo() + ": " + e); |
306 } |
293 } |
307 } |
294 } |
308 String makeIndyType(int id) { |
295 |
|
296 String makeIndyType() { |
309 StringBuilder buf = new StringBuilder(); |
297 StringBuilder buf = new StringBuilder(); |
310 buf.append("("); |
298 buf.append("("); |
311 if (!mk2.isStatic()) { |
299 if (!mk2.isStatic()) { |
312 buf.append(String.format("LTest%d;", id)); |
300 buf.append("LTest;"); |
313 } |
301 } |
314 buf.append(")Ljava/lang/Runnable;"); |
302 buf.append(")Ljava/lang/Runnable;"); |
315 return buf.toString(); |
303 return buf.toString(); |
316 } |
304 } |
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 } |
305 } |