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