8027232: Update j.l.invoke code generating class files to use ASM enhancements for invocation of non-abstract methods on ifaces
Reviewed-by: ksrini, rfield
Contributed-by: john.r.rose@oracle.com, paul.sandoz@oracle.com
--- a/jdk/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Wed Nov 06 11:22:15 2013 -0800
+++ b/jdk/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Wed Nov 06 11:31:49 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -51,7 +51,7 @@
/* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
- private static final int CLASSFILE_VERSION = 51;
+ private static final int CLASSFILE_VERSION = 52;
private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
private static final String NAME_CTOR = "<init>";
@@ -465,7 +465,9 @@
convertArgumentTypes(methodType);
// Invoke the method we want to forward to
- visitMethodInsn(invocationOpcode(), implMethodClassName, implMethodName, implMethodDesc);
+ visitMethodInsn(invocationOpcode(), implMethodClassName,
+ implMethodName, implMethodDesc,
+ implDefiningClass.isInterface());
// Convert the return value (if any) and return it
// Note: if adapting from non-void to void, the 'return'
--- a/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Wed Nov 06 11:22:15 2013 -0800
+++ b/jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Wed Nov 06 11:31:49 2013 -0800
@@ -275,7 +275,7 @@
*/
private void classFilePrologue() {
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
- cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, superName, null);
+ cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, className, null, superName, null);
cw.visitSource(sourceFile, null);
String invokerDesc = invokerType.toMethodDescriptorString();
@@ -646,7 +646,8 @@
// invocation
if (member.isMethod()) {
mtype = member.getMethodType().toMethodDescriptorString();
- mv.visitMethodInsn(refKindOpcode(refKind), cname, mname, mtype);
+ mv.visitMethodInsn(refKindOpcode(refKind), cname, mname, mtype,
+ member.getDeclaringClass().isInterface());
} else {
mtype = MethodType.toFieldDescriptorString(member.getFieldType());
mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype);
--- a/jdk/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java Wed Nov 06 11:22:15 2013 -0800
+++ b/jdk/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java Wed Nov 06 11:31:49 2013 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -35,7 +35,7 @@
class TypeConvertingMethodAdapter extends MethodVisitor {
TypeConvertingMethodAdapter(MethodVisitor mv) {
- super(Opcodes.ASM4, mv);
+ super(Opcodes.ASM5, mv);
}
private static final int NUM_WRAPPERS = Wrapper.values().length;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/lambda/LambdaAsm.java Wed Nov 06 11:31:49 2013 -0800
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8027232
+ * @summary ensures that j.l.i.InvokerByteCodeGenerator and ASM visitMethodInsn
+ * generate bytecodes with correct constant pool references
+ * @compile -XDignore.symbol.file LambdaAsm.java LUtils.java
+ * @run main/othervm LambdaAsm
+ */
+import com.sun.tools.classfile.Attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.Code_attribute;
+import com.sun.tools.classfile.ConstantPool;
+import com.sun.tools.classfile.ConstantPool.CPInfo;
+import com.sun.tools.classfile.Instruction;
+import com.sun.tools.classfile.Method;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.util.ArrayList;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+
+import static java.nio.file.Files.*;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+public class LambdaAsm {
+
+ static final File TestFile = new File("A.java");
+
+ static void init() {
+ emitCode();
+ LUtils.compile(TestFile.getName());
+ LUtils.TestResult tr = LUtils.doExec(LUtils.JAVA_CMD.getAbsolutePath(),
+ "-Djdk.internal.lambda.dumpProxyClasses=.",
+ "-cp", ".", "A");
+ if (tr.exitValue != 0) {
+ System.out.println("Error: " + tr.toString());
+ throw new RuntimeException("could not create proxy classes");
+ }
+ }
+
+ static void emitCode() {
+ ArrayList<String> scratch = new ArrayList<>();
+ scratch.add("import java.util.function.*;");
+ scratch.add("class A {");
+ scratch.add(" interface I {");
+ scratch.add(" default Supplier<Integer> a() { return () -> 1; }");
+ scratch.add(" default Supplier<Integer> b(int i) { return () -> i; }");
+ scratch.add(" default Supplier<Integer> c(int i) { return () -> m(i); }");
+ scratch.add(" int m(int i);");
+ scratch.add(" static Integer d() { return 0; }");
+ scratch.add(" }");
+ scratch.add(" static class C implements I {");
+ scratch.add(" public int m(int i) { return i;}");
+ scratch.add(" }");
+ scratch.add(" public static void main(String[] args) {");
+ scratch.add(" I i = new C();");
+ scratch.add(" i.a();");
+ scratch.add(" i.b(1);");
+ scratch.add(" i.c(1);");
+ scratch.add(" I.d();");
+ scratch.add(" }");
+ scratch.add("}");
+ LUtils.createFile(TestFile, scratch);
+ }
+
+ static void checkMethod(String cname, String mname, ConstantPool cp,
+ Code_attribute code) throws ConstantPool.InvalidIndex {
+ for (Instruction i : code.getInstructions()) {
+ String iname = i.getMnemonic();
+ if ("invokespecial".equals(iname)
+ || "invokestatic".equals(iname)) {
+ int idx = i.getByte(2);
+ System.out.println("Verifying " + cname + ":" + mname +
+ " instruction:" + iname + " index @" + idx);
+ CPInfo cpinfo = cp.get(idx);
+ if (cpinfo instanceof ConstantPool.CONSTANT_Methodref_info) {
+ throw new RuntimeException("unexpected CP type expected "
+ + "InterfaceMethodRef, got MethodRef, " + cname
+ + ", " + mname);
+ }
+ }
+ }
+ }
+
+ static int checkMethod(ClassFile cf, String mthd) throws Exception {
+ if (cf.major_version < 52) {
+ throw new RuntimeException("unexpected class file version, in "
+ + cf.getName() + "expected 52, got " + cf.major_version);
+ }
+ int count = 0;
+ for (Method m : cf.methods) {
+ String mname = m.getName(cf.constant_pool);
+ if (mname.equals(mthd)) {
+ for (Attribute a : m.attributes) {
+ if ("Code".equals(a.getName(cf.constant_pool))) {
+ count++;
+ checkMethod(cf.getName(), mname, cf.constant_pool,
+ (Code_attribute) a);
+ }
+ }
+ }
+ }
+ return count;
+ }
+
+ static void verifyInvokerBytecodeGenerator() throws Exception {
+ int count = 0;
+ int mcount = 0;
+ try (DirectoryStream<Path> ds = newDirectoryStream(new File(".").toPath(),
+ // filter in lambda proxy classes
+ "A$I$$Lambda$?.class")) {
+ for (Path p : ds) {
+ System.out.println(p.toFile());
+ ClassFile cf = ClassFile.read(p.toFile());
+ // Check those methods implementing Supplier.get
+ mcount += checkMethod(cf, "get");
+ count++;
+ }
+ }
+ if (count < 3) {
+ throw new RuntimeException("unexpected number of files, "
+ + "expected atleast 3 files, but got only " + count);
+ }
+ if (mcount < 3) {
+ throw new RuntimeException("unexpected number of methods, "
+ + "expected atleast 3 methods, but got only " + mcount);
+ }
+ }
+
+ static void verifyASM() throws Exception {
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(V1_8, ACC_PUBLIC, "X", null, "java/lang/Object", null);
+ MethodVisitor mv = cw.visitMethod(ACC_STATIC, "foo",
+ "()V", null, null);
+ mv.visitMaxs(2, 1);
+ mv.visitMethodInsn(INVOKESTATIC,
+ "java/util/function/Function.class",
+ "identity", "()Ljava/util/function/Function;", true);
+ mv.visitInsn(RETURN);
+ cw.visitEnd();
+ byte[] carray = cw.toByteArray();
+ // for debugging
+ // write((new File("X.class")).toPath(), carray, CREATE, TRUNCATE_EXISTING);
+
+ // verify using javap/classfile reader
+ ClassFile cf = ClassFile.read(new ByteArrayInputStream(carray));
+ int mcount = checkMethod(cf, "foo");
+ if (mcount < 1) {
+ throw new RuntimeException("unexpected method count, expected 1" +
+ "but got " + mcount);
+ }
+ }
+
+ public static void main(String... args) throws Exception {
+ init();
+ verifyInvokerBytecodeGenerator();
+ verifyASM();
+ }
+}