8027232: Update j.l.invoke code generating class files to use ASM enhancements for invocation of non-abstract methods on ifaces
authorksrini
Wed, 06 Nov 2013 11:31:49 -0800
changeset 21618 6632680b0683
parent 21617 53b88e4ac6ed
child 21619 bd60e99ca531
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
jdk/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
jdk/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
jdk/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java
jdk/test/java/lang/invoke/lambda/LambdaAsm.java
--- 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();
+    }
+}