1 /*
  2  * Copyright (c) 2013, 2023, 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 8027232
 27  * @library /test/lib/
 28  * @modules java.base/jdk.internal.org.objectweb.asm
 29  *          jdk.jdeps/com.sun.tools.classfile
 30  *          jdk.zipfs
 31  * @compile LambdaAsm.java
 32  * @run main/othervm LambdaAsm
 33  * @summary ensures that j.l.i.InvokerByteCodeGenerator and ASM visitMethodInsn
 34  * generate  bytecodes with correct constant pool references
 35  */
 36 import com.sun.tools.classfile.Attribute;
 37 import com.sun.tools.classfile.ClassFile;
 38 import com.sun.tools.classfile.Code_attribute;
 39 import com.sun.tools.classfile.ConstantPool;
 40 import com.sun.tools.classfile.ConstantPool.CPInfo;
 41 import com.sun.tools.classfile.Instruction;
 42 import com.sun.tools.classfile.Method;
 43 import java.io.ByteArrayInputStream;
 44 import java.io.IOException;
 45 import java.nio.charset.Charset;
 46 import java.nio.file.Files;
 47 import java.util.ArrayList;
 48 import java.nio.file.DirectoryStream;
 49 import java.nio.file.Path;
 50 import jdk.internal.org.objectweb.asm.ClassWriter;
 51 import jdk.internal.org.objectweb.asm.MethodVisitor;
 52 import jdk.test.lib.compiler.CompilerUtils;
 53 import jdk.test.lib.process.OutputAnalyzer;
 54 
 55 import static java.nio.file.Files.*;
 56 import static jdk.internal.org.objectweb.asm.Opcodes.*;
 57 import static jdk.test.lib.process.ProcessTools.*;
 58 
 59 public class LambdaAsm {
 60     static final Path DUMP_LAMBDA_PROXY_CLASS_FILES = Path.of("DUMP_LAMBDA_PROXY_CLASS_FILES");
 61     static final Path SRC = Path.of("src");
 62     static final Path CLASSES = Path.of("classes");
 63 
 64     static void init() throws Exception {
 65         emitCode();
 66         CompilerUtils.compile(SRC, CLASSES);
 67         OutputAnalyzer outputAnalyzer = executeProcess(createTestJavaProcessBuilder(
 68                 "-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true",
 69                 "-cp", CLASSES.toString(), "A"));
 70         outputAnalyzer.shouldHaveExitValue(0);
 71     }
 72 
 73     static void emitCode() throws IOException {
 74         ArrayList<String> scratch = new ArrayList<>();
 75         scratch.add("import java.util.function.*;");
 76         scratch.add("class A {");
 77         scratch.add("   interface I {");
 78         scratch.add("       default Supplier<Integer> a() { return () -> 1; }");
 79         scratch.add("       default Supplier<Integer> b(int i) { return () -> i; }");
 80         scratch.add("       default Supplier<Integer> c(int i) { return () -> m(i); }");
 81         scratch.add("       int m(int i);");
 82         scratch.add("       static Integer d() { return 0; }");
 83         scratch.add("   }");
 84         scratch.add("   static class C implements I {");
 85         scratch.add("       public int m(int i) { return i;}");
 86         scratch.add("   }");
 87         scratch.add("   public static void main(String[] args) {");
 88         scratch.add("       I i = new C();");
 89         scratch.add("       i.a();");
 90         scratch.add("       i.b(1);");
 91         scratch.add("       i.c(1);");
 92         scratch.add("       I.d();");
 93         scratch.add("   }");
 94         scratch.add("}");
 95 
 96         Path testFile = SRC.resolve("A.java");
 97         Files.createDirectories(SRC);
 98         Files.write(testFile, scratch, Charset.defaultCharset());
 99     }
100 
101     static void checkMethod(String cname, String mname, ConstantPool cp,
102             Code_attribute code) throws ConstantPool.InvalidIndex {
103         for (Instruction i : code.getInstructions()) {
104             String iname = i.getMnemonic();
105             if ("invokespecial".equals(iname)
106                     || "invokestatic".equals(iname)) {
107                 int idx = i.getByte(2);
108                 System.out.println("Verifying " + cname + ":" + mname +
109                         " instruction:" + iname + " index @" + idx);
110                 CPInfo cpinfo = cp.get(idx);
111                 if (cpinfo instanceof ConstantPool.CONSTANT_Methodref_info) {
112                     throw new RuntimeException("unexpected CP type expected "
113                             + "InterfaceMethodRef, got MethodRef, " + cname
114                             + ", " + mname);
115                 }
116             }
117         }
118     }
119 
120     static int checkMethod(ClassFile cf, String mthd) throws Exception {
121         if (cf.major_version < 52) {
122             throw new RuntimeException("unexpected class file version, in "
123                     + cf.getName() + "expected 52, got " + cf.major_version);
124         }
125         int count = 0;
126         for (Method m : cf.methods) {
127             String mname = m.getName(cf.constant_pool);
128             if (mname.equals(mthd)) {
129                 for (Attribute a : m.attributes) {
130                     if ("Code".equals(a.getName(cf.constant_pool))) {
131                         count++;
132                         checkMethod(cf.getName(), mname, cf.constant_pool,
133                                 (Code_attribute) a);
134                     }
135                 }
136             }
137         }
138         return count;
139     }
140 
141     static void verifyInvokerBytecodeGenerator() throws Exception {
142         int count = 0;
143         int mcount = 0;
144         try (DirectoryStream<Path> ds = newDirectoryStream(DUMP_LAMBDA_PROXY_CLASS_FILES,
145                 // filter in lambda proxy classes
146                 "A$I$$Lambda.*.class")) {
147             for (Path p : ds) {
148                 System.out.println(p.toFile());
149                 ClassFile cf = ClassFile.read(p.toFile());
150                 // Check those methods implementing Supplier.get
151                 mcount += checkMethod(cf, "get");
152                 count++;
153             }
154         }
155         if (count < 3) {
156             throw new RuntimeException("unexpected number of files, "
157                     + "expected atleast 3 files, but got only " + count);
158         }
159         if (mcount < 3) {
160             throw new RuntimeException("unexpected number of methods, "
161                     + "expected atleast 3 methods, but got only " + mcount);
162         }
163     }
164 
165     static void verifyASM() throws Exception {
166         ClassWriter cw = new ClassWriter(0);
167         cw.visit(V1_8, ACC_PUBLIC, "X", null, "java/lang/Object", null);
168         MethodVisitor mv = cw.visitMethod(ACC_STATIC, "foo",
169                 "()V", null, null);
170         mv.visitMaxs(2, 1);
171         mv.visitMethodInsn(INVOKESTATIC,
172                 "java/util/function/Function.class",
173                 "identity", "()Ljava/util/function/Function;", true);
174         mv.visitInsn(RETURN);
175         cw.visitEnd();
176         byte[] carray = cw.toByteArray();
177         // for debugging
178         // write((new File("X.class")).toPath(), carray, CREATE, TRUNCATE_EXISTING);
179 
180         // verify using javap/classfile reader
181         ClassFile cf = ClassFile.read(new ByteArrayInputStream(carray));
182         int mcount = checkMethod(cf, "foo");
183         if (mcount < 1) {
184             throw new RuntimeException("unexpected method count, expected 1" +
185                     "but got " + mcount);
186         }
187     }
188 
189     public static void main(String... args) throws Exception {
190         init();
191         verifyInvokerBytecodeGenerator();
192         verifyASM();
193     }
194 }