Tue, 14 May 2013 11:11:09 -0700
8012556: Implement lambda methods on interfaces as static
8006140: Javac NPE compiling Lambda expression on initialization expression of static field in interface
Summary: Lambdas occurring in static contexts or those not needing instance information should be generated into static methods. This has long been the case for classes. However, as a work-around to the lack of support for statics on interfaces, interface lambda methods have been generated into default methods. For lambdas in interface static contexts (fields and static methods) this causes an NPE in javac because there is no 'this'. MethodHandles now support static methods on interfaces. This changeset allows lambda methods to be generated as static interface methods. An existing bug in Hotspot (8013875) is exposed in a test when the "-esa" flag is used. This test and another test that already exposed this bug have been marked with @ignore.
Reviewed-by: mcimadamore
1 /*
2 * Copyright (c) 2012, 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 */
24 /*
25 * @test
26 * @bug 7194586
27 * @bug 8003280 8006694 8010404
28 * @summary Add lambda tests
29 * Add back-end support for invokedynamic
30 * temporarily workaround combo tests are causing time out in several platforms
31 * @library ../lib
32 * @build JavacTestingAbstractThreadedTest
33 * @run main/othervm TestInvokeDynamic
34 */
36 // use /othervm to avoid jtreg timeout issues (CODETOOLS-7900047)
37 // see JDK-8006746
39 import com.sun.source.tree.MethodInvocationTree;
40 import com.sun.source.tree.MethodTree;
41 import com.sun.source.util.TaskEvent;
42 import com.sun.source.util.TaskListener;
43 import com.sun.source.util.TreeScanner;
45 import com.sun.tools.classfile.Attribute;
46 import com.sun.tools.classfile.BootstrapMethods_attribute;
47 import com.sun.tools.classfile.ClassFile;
48 import com.sun.tools.classfile.Code_attribute;
49 import com.sun.tools.classfile.ConstantPool.*;
50 import com.sun.tools.classfile.Instruction;
51 import com.sun.tools.classfile.LineNumberTable_attribute;
52 import com.sun.tools.classfile.Method;
54 import com.sun.tools.javac.api.JavacTaskImpl;
55 import com.sun.tools.javac.code.Symbol;
56 import com.sun.tools.javac.code.Symbol.MethodSymbol;
57 import com.sun.tools.javac.code.Symtab;
58 import com.sun.tools.javac.code.Types;
59 import com.sun.tools.javac.jvm.Pool;
60 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
61 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
62 import com.sun.tools.javac.tree.JCTree.JCIdent;
63 import com.sun.tools.javac.util.Context;
64 import com.sun.tools.javac.util.Names;
66 import java.io.File;
67 import java.net.URI;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Locale;
72 import javax.tools.Diagnostic;
73 import javax.tools.JavaFileObject;
74 import javax.tools.SimpleJavaFileObject;
76 import static com.sun.tools.javac.jvm.ClassFile.*;
78 public class TestInvokeDynamic
79 extends JavacTestingAbstractThreadedTest
80 implements Runnable {
82 enum StaticArgumentKind {
83 STRING("Hello!", "String", "Ljava/lang/String;") {
84 @Override
85 boolean check(CPInfo cpInfo) throws Exception {
86 return (cpInfo instanceof CONSTANT_String_info) &&
87 ((CONSTANT_String_info)cpInfo).getString()
88 .equals(value);
89 }
90 },
91 CLASS(null, "Class<?>", "Ljava/lang/Class;") {
92 @Override
93 boolean check(CPInfo cpInfo) throws Exception {
94 return (cpInfo instanceof CONSTANT_Class_info) &&
95 ((CONSTANT_Class_info)cpInfo).getName()
96 .equals("java/lang/String");
97 }
98 },
99 INTEGER(1, "int", "I") {
100 @Override
101 boolean check(CPInfo cpInfo) throws Exception {
102 return (cpInfo instanceof CONSTANT_Integer_info) &&
103 ((CONSTANT_Integer_info)cpInfo).value ==
104 ((Integer)value).intValue();
105 }
106 },
107 LONG(1L, "long", "J") {
108 @Override
109 boolean check(CPInfo cpInfo) throws Exception {
110 return (cpInfo instanceof CONSTANT_Long_info) &&
111 ((CONSTANT_Long_info)cpInfo).value ==
112 ((Long)value).longValue();
113 }
114 },
115 FLOAT(1.0f, "float", "F") {
116 @Override
117 boolean check(CPInfo cpInfo) throws Exception {
118 return (cpInfo instanceof CONSTANT_Float_info) &&
119 ((CONSTANT_Float_info)cpInfo).value ==
120 ((Float)value).floatValue();
121 }
122 },
123 DOUBLE(1.0, "double","D") {
124 @Override
125 boolean check(CPInfo cpInfo) throws Exception {
126 return (cpInfo instanceof CONSTANT_Double_info) &&
127 ((CONSTANT_Double_info)cpInfo).value ==
128 ((Double)value).doubleValue();
129 }
130 },
131 METHOD_HANDLE(null, "MethodHandle", "Ljava/lang/invoke/MethodHandle;") {
132 @Override
133 boolean check(CPInfo cpInfo) throws Exception {
134 if (!(cpInfo instanceof CONSTANT_MethodHandle_info))
135 return false;
136 CONSTANT_MethodHandle_info handleInfo =
137 (CONSTANT_MethodHandle_info)cpInfo;
138 return handleInfo.getCPRefInfo().getClassName().equals("Array") &&
139 handleInfo.reference_kind == RefKind.REF_invokeVirtual &&
140 handleInfo.getCPRefInfo()
141 .getNameAndTypeInfo().getName().equals("clone") &&
142 handleInfo.getCPRefInfo()
143 .getNameAndTypeInfo().getType().equals("()Ljava/lang/Object;");
144 }
145 },
146 METHOD_TYPE(null, "MethodType", "Ljava/lang/invoke/MethodType;") {
147 @Override
148 boolean check(CPInfo cpInfo) throws Exception {
149 return (cpInfo instanceof CONSTANT_MethodType_info) &&
150 ((CONSTANT_MethodType_info)cpInfo).getType()
151 .equals("()Ljava/lang/Object;");
152 }
153 };
155 Object value;
156 String sourceTypeStr;
157 String bytecodeTypeStr;
159 StaticArgumentKind(Object value, String sourceTypeStr,
160 String bytecodeTypeStr) {
161 this.value = value;
162 this.sourceTypeStr = sourceTypeStr;
163 this.bytecodeTypeStr = bytecodeTypeStr;
164 }
166 abstract boolean check(CPInfo cpInfo) throws Exception;
168 Object getValue(Symtab syms, Names names, Types types) {
169 switch (this) {
170 case STRING:
171 case INTEGER:
172 case LONG:
173 case FLOAT:
174 case DOUBLE:
175 return value;
176 case CLASS:
177 return syms.stringType.tsym;
178 case METHOD_HANDLE:
179 return new Pool.MethodHandle(REF_invokeVirtual,
180 syms.arrayCloneMethod, types);
181 case METHOD_TYPE:
182 return syms.arrayCloneMethod.type;
183 default:
184 throw new AssertionError();
185 }
186 }
187 }
189 enum StaticArgumentsArity {
190 ZERO(0),
191 ONE(1),
192 TWO(2),
193 THREE(3);
195 int arity;
197 StaticArgumentsArity(int arity) {
198 this.arity = arity;
199 }
200 }
202 public static void main(String... args) throws Exception {
203 for (StaticArgumentsArity arity : StaticArgumentsArity.values()) {
204 if (arity.arity == 0) {
205 pool.execute(new TestInvokeDynamic(arity));
206 } else {
207 for (StaticArgumentKind sak1 : StaticArgumentKind.values()) {
208 if (arity.arity == 1) {
209 pool.execute(new TestInvokeDynamic(arity, sak1));
210 } else {
211 for (StaticArgumentKind sak2 : StaticArgumentKind.values()) {
212 if (arity.arity == 2) {
213 pool.execute(new TestInvokeDynamic(arity, sak1, sak2));
214 } else {
215 for (StaticArgumentKind sak3 : StaticArgumentKind.values()) {
216 pool.execute(
217 new TestInvokeDynamic(arity, sak1, sak2, sak3));
218 }
219 }
220 }
221 }
222 }
223 }
224 }
226 checkAfterExec();
227 }
229 StaticArgumentsArity arity;
230 StaticArgumentKind[] saks;
231 DiagChecker dc;
233 TestInvokeDynamic(StaticArgumentsArity arity, StaticArgumentKind... saks) {
234 this.arity = arity;
235 this.saks = saks;
236 dc = new DiagChecker();
237 }
239 public void run() {
240 int id = checkCount.incrementAndGet();
241 JavaSource source = new JavaSource(id);
242 JavacTaskImpl ct = (JavacTaskImpl)comp.getTask(null, fm.get(), dc,
243 Arrays.asList("-g"), null, Arrays.asList(source));
244 Context context = ct.getContext();
245 Symtab syms = Symtab.instance(context);
246 Names names = Names.instance(context);
247 Types types = Types.instance(context);
248 ct.addTaskListener(new Indifier(syms, names, types));
249 try {
250 ct.generate();
251 } catch (Throwable t) {
252 t.printStackTrace();
253 throw new AssertionError(
254 String.format("Error thrown when compiling following code\n%s",
255 source.source));
256 }
257 if (dc.diagFound) {
258 throw new AssertionError(
259 String.format("Diags found when compiling following code\n%s\n\n%s",
260 source.source, dc.printDiags()));
261 }
262 verifyBytecode(id);
263 }
265 void verifyBytecode(int id) {
266 File compiledTest = new File(String.format("Test%d.class", id));
267 try {
268 ClassFile cf = ClassFile.read(compiledTest);
269 Method testMethod = null;
270 for (Method m : cf.methods) {
271 if (m.getName(cf.constant_pool).equals("test")) {
272 testMethod = m;
273 break;
274 }
275 }
276 if (testMethod == null) {
277 throw new Error("Test method not found");
278 }
279 Code_attribute ea =
280 (Code_attribute)testMethod.attributes.get(Attribute.Code);
281 if (testMethod == null) {
282 throw new Error("Code attribute for test() method not found");
283 }
285 int bsmIdx = -1;
287 for (Instruction i : ea.getInstructions()) {
288 if (i.getMnemonic().equals("invokedynamic")) {
289 CONSTANT_InvokeDynamic_info indyInfo =
290 (CONSTANT_InvokeDynamic_info)cf
291 .constant_pool.get(i.getShort(1));
292 bsmIdx = indyInfo.bootstrap_method_attr_index;
293 if (!indyInfo.getNameAndTypeInfo().getType().equals("()V")) {
294 throw new
295 AssertionError("type mismatch for CONSTANT_InvokeDynamic_info");
296 }
297 }
298 }
299 if (bsmIdx == -1) {
300 throw new Error("Missing invokedynamic in generated code");
301 }
303 BootstrapMethods_attribute bsm_attr =
304 (BootstrapMethods_attribute)cf
305 .getAttribute(Attribute.BootstrapMethods);
306 if (bsm_attr.bootstrap_method_specifiers.length != 1) {
307 throw new Error("Bad number of method specifiers " +
308 "in BootstrapMethods attribute");
309 }
310 BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
311 bsm_attr.bootstrap_method_specifiers[0];
313 if (bsm_spec.bootstrap_arguments.length != arity.arity) {
314 throw new Error("Bad number of static invokedynamic args " +
315 "in BootstrapMethod attribute");
316 }
318 int count = 0;
319 for (StaticArgumentKind sak : saks) {
320 if (!sak.check(cf.constant_pool
321 .get(bsm_spec.bootstrap_arguments[count]))) {
322 throw new Error("Bad static argument value " + sak);
323 }
324 count++;
325 }
327 CONSTANT_MethodHandle_info bsm_handle =
328 (CONSTANT_MethodHandle_info)cf.constant_pool
329 .get(bsm_spec.bootstrap_method_ref);
331 if (bsm_handle.reference_kind != RefKind.REF_invokeStatic) {
332 throw new Error("Bad kind on boostrap method handle");
333 }
335 CONSTANT_Methodref_info bsm_ref =
336 (CONSTANT_Methodref_info)cf.constant_pool
337 .get(bsm_handle.reference_index);
339 if (!bsm_ref.getClassInfo().getName().equals("Bootstrap")) {
340 throw new Error("Bad owner of boostrap method");
341 }
343 if (!bsm_ref.getNameAndTypeInfo().getName().equals("bsm")) {
344 throw new Error("Bad boostrap method name");
345 }
347 if (!bsm_ref.getNameAndTypeInfo()
348 .getType().equals(asBSMSignatureString())) {
349 throw new Error("Bad boostrap method type" +
350 bsm_ref.getNameAndTypeInfo().getType() + " " +
351 asBSMSignatureString());
352 }
354 LineNumberTable_attribute lnt =
355 (LineNumberTable_attribute)ea.attributes.get(Attribute.LineNumberTable);
357 if (lnt == null) {
358 throw new Error("No LineNumberTable attribute");
359 }
360 if (lnt.line_number_table_length != 2) {
361 throw new Error("Wrong number of entries in LineNumberTable");
362 }
363 } catch (Exception e) {
364 e.printStackTrace();
365 throw new Error("error reading " + compiledTest +": " + e);
366 }
367 }
369 String asBSMSignatureString() {
370 StringBuilder buf = new StringBuilder();
371 buf.append("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;");
372 for (StaticArgumentKind sak : saks) {
373 buf.append(sak.bytecodeTypeStr);
374 }
375 buf.append(")Ljava/lang/invoke/CallSite;");
376 return buf.toString();
377 }
379 class JavaSource extends SimpleJavaFileObject {
381 static final String source_template = "import java.lang.invoke.*;\n" +
382 "class Bootstrap {\n" +
383 " public static CallSite bsm(MethodHandles.Lookup lookup, " +
384 "String name, MethodType methodType #SARGS) {\n" +
385 " return null;\n" +
386 " }\n" +
387 "}\n" +
388 "class Test#ID {\n" +
389 " void m() { }\n" +
390 " void test() {\n" +
391 " Object o = this; // marker statement \n" +
392 " m();\n" +
393 " }\n" +
394 "}";
396 String source;
398 JavaSource(int id) {
399 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
400 source = source_template.replace("#SARGS", asSignatureString())
401 .replace("#ID", String.valueOf(id));
402 }
404 @Override
405 public CharSequence getCharContent(boolean ignoreEncodingErrors) {
406 return source;
407 }
409 String asSignatureString() {
410 int count = 0;
411 StringBuilder buf = new StringBuilder();
412 for (StaticArgumentKind sak : saks) {
413 buf.append(",");
414 buf.append(sak.sourceTypeStr);
415 buf.append(' ');
416 buf.append(String.format("x%d", count++));
417 }
418 return buf.toString();
419 }
420 }
422 class Indifier extends TreeScanner<Void, Void> implements TaskListener {
424 MethodSymbol bsm;
425 Symtab syms;
426 Names names;
427 Types types;
429 Indifier(Symtab syms, Names names, Types types) {
430 this.syms = syms;
431 this.names = names;
432 this.types = types;
433 }
435 @Override
436 public void started(TaskEvent e) {
437 //do nothing
438 }
440 @Override
441 public void finished(TaskEvent e) {
442 if (e.getKind() == TaskEvent.Kind.ANALYZE) {
443 scan(e.getCompilationUnit(), null);
444 }
445 }
447 @Override
448 public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
449 super.visitMethodInvocation(node, p);
450 JCMethodInvocation apply = (JCMethodInvocation)node;
451 JCIdent ident = (JCIdent)apply.meth;
452 Symbol oldSym = ident.sym;
453 if (!oldSym.isConstructor()) {
454 Object[] staticArgs = new Object[arity.arity];
455 for (int i = 0; i < arity.arity ; i++) {
456 staticArgs[i] = saks[i].getValue(syms, names, types);
457 }
458 ident.sym = new Symbol.DynamicMethodSymbol(oldSym.name,
459 oldSym.owner, REF_invokeStatic, bsm, oldSym.type, staticArgs);
460 }
461 return null;
462 }
464 @Override
465 public Void visitMethod(MethodTree node, Void p) {
466 super.visitMethod(node, p);
467 if (node.getName().toString().equals("bsm")) {
468 bsm = ((JCMethodDecl)node).sym;
469 }
470 return null;
471 }
472 }
474 static class DiagChecker
475 implements javax.tools.DiagnosticListener<JavaFileObject> {
477 boolean diagFound;
478 ArrayList<String> diags = new ArrayList<>();
480 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
481 diags.add(diagnostic.getMessage(Locale.getDefault()));
482 diagFound = true;
483 }
485 String printDiags() {
486 StringBuilder buf = new StringBuilder();
487 for (String s : diags) {
488 buf.append(s);
489 buf.append("\n");
490 }
491 return buf.toString();
492 }
493 }
495 }