1/*
2 * Copyright (c) 2015, 2016, 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 */
23package org.graalvm.compiler.core.test;
24
25import java.lang.invoke.MethodHandle;
26import java.lang.invoke.MethodHandles;
27import java.lang.invoke.MethodType;
28
29import org.graalvm.compiler.code.CompilationResult;
30import org.graalvm.compiler.debug.DebugContext;
31import org.graalvm.compiler.test.ExportingClassLoader;
32import org.junit.Test;
33import org.objectweb.asm.ClassWriter;
34import org.objectweb.asm.Label;
35import org.objectweb.asm.MethodVisitor;
36import org.objectweb.asm.Opcodes;
37
38import jdk.vm.ci.code.InstalledCode;
39import jdk.vm.ci.meta.ResolvedJavaMethod;
40
41public final class InterfaceMethodHandleTest extends GraalCompilerTest {
42    private static final MethodHandle INTERFACE_HANDLE_M;
43    private static final MethodHandle INTERFACE_HANDLE_M2;
44
45    public interface I {
46        int m();
47
48        int m2(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j);
49    }
50
51    static class A implements I {
52        @Override
53        public int m() {
54            return 0;
55        }
56
57        @Override
58        public int m2(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
59            return 1;
60        }
61
62    }
63
64    static class M2Thrower implements I {
65        @Override
66        public int m() {
67            return 0;
68        }
69
70        @Override
71        public int m2(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
72            throw new InternalError();
73        }
74
75    }
76
77    static {
78        try {
79            MethodType type = MethodType.fromMethodDescriptorString("()I", I.class.getClassLoader());
80            INTERFACE_HANDLE_M = MethodHandles.lookup().findVirtual(I.class, "m", type);
81            MethodType type2 = MethodType.fromMethodDescriptorString("(IIIIIIIIII)I", I.class.getClassLoader());
82            INTERFACE_HANDLE_M2 = MethodHandles.lookup().findVirtual(I.class, "m2", type2);
83        } catch (IllegalAccessException | NoSuchMethodException e) {
84            throw new RuntimeException("unable to initialize method handle", e);
85        }
86    }
87
88    public static Object invokeInterfaceHandle(I o) throws Throwable {
89        return (int) INTERFACE_HANDLE_M.invokeExact(o);
90    }
91
92    @Test
93    public void testInvokeInterface01() {
94        test("invokeInterfaceHandle", new A());
95
96    }
97
98    @Test
99    public void testInvokeInterface02() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
100        test("invokeInterfaceHandle", loader.findClass(NAME).newInstance());
101    }
102
103    public static Object invokeInterfaceHandle2(I o, int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) throws Throwable {
104        return (int) INTERFACE_HANDLE_M2.invokeExact(o, a, b, c, d, e, f, g, h, i, j);
105    }
106
107    @Override
108    protected InstalledCode addMethod(DebugContext debug, ResolvedJavaMethod method, CompilationResult compResult) {
109        if (method.getDeclaringClass().equals(getMetaAccess().lookupJavaType(M2Thrower.class))) {
110            // Make sure M2Thrower.m2 is invoked from normal code
111            return getBackend().createDefaultInstalledCode(debug, method, compResult);
112        }
113        return super.addMethod(debug, method, compResult);
114    }
115
116    /**
117     * Try to exercise a mixed calling sequence with regular JIT code calling a method handle that
118     * can't be inlined with an implementation compiled by Graal that throws an exception.
119     */
120    @Test
121    public void testInvokeInterface03() throws Throwable {
122        A goodInstance = new A();
123        I badInstance = new M2Thrower();
124        getCode(getMetaAccess().lookupJavaMethod(getMethod(M2Thrower.class, "m2")));
125        for (int x = 0; x < 1000; x++) {
126            final int limit = 20000;
127            for (int i = 0; i <= limit; i++) {
128                try {
129                    invokeInterfaceHandle2(i < limit - 1 ? goodInstance : badInstance, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
130                } catch (InternalError e) {
131
132                }
133            }
134        }
135    }
136
137    private static final String BASENAME = InterfaceMethodHandleTest.class.getName();
138    private static final String NAME = BASENAME + "_B";
139    private final AsmLoader loader;
140
141    public InterfaceMethodHandleTest() {
142        exportPackage(JAVA_BASE, "jdk.internal.org.objectweb.asm");
143        loader = new AsmLoader(UnbalancedMonitorsTest.class.getClassLoader());
144    }
145
146    static class Gen implements Opcodes {
147        /**
148         * Construct a type which claims to implement {@link I} but with incorrect access on
149         * {@link I#m} so that an exception must be thrown.
150         */
151        public static byte[] bytesForB() {
152
153            ClassWriter cw = new ClassWriter(0);
154            MethodVisitor mv;
155            String jvmName = NAME.replace('.', '/');
156            cw.visit(52, ACC_SUPER | ACC_PUBLIC, jvmName, null, "java/lang/Object", new String[]{BASENAME.replace('.', '/') + "$I"});
157
158            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
159            mv.visitCode();
160            Label l0 = new Label();
161            mv.visitLabel(l0);
162            mv.visitVarInsn(ALOAD, 0);
163            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
164            mv.visitInsn(RETURN);
165            Label l1 = new Label();
166            mv.visitLabel(l1);
167            mv.visitMaxs(1, 1);
168            mv.visitEnd();
169
170            mv = cw.visitMethod(ACC_PRIVATE, "m", "()I", null, null);
171            mv.visitCode();
172            l0 = new Label();
173            mv.visitLabel(l0);
174            mv.visitInsn(ICONST_0);
175            mv.visitInsn(IRETURN);
176            l1 = new Label();
177            mv.visitLabel(l1);
178            mv.visitMaxs(1, 1);
179            mv.visitEnd();
180
181            cw.visitEnd();
182
183            mv = cw.visitMethod(ACC_PRIVATE, "m2", "(IIIIIIIIII)I", null, null);
184            mv.visitCode();
185            l0 = new Label();
186            mv.visitLabel(l0);
187            mv.visitInsn(ICONST_0);
188            mv.visitInsn(IRETURN);
189            l1 = new Label();
190            mv.visitLabel(l1);
191            mv.visitMaxs(1, 11);
192            mv.visitEnd();
193
194            cw.visitEnd();
195
196            return cw.toByteArray();
197        }
198    }
199
200    public static class AsmLoader extends ExportingClassLoader {
201        Class<?> loaded;
202
203        public AsmLoader(ClassLoader parent) {
204            super(parent);
205        }
206
207        @Override
208        protected Class<?> findClass(String name) throws ClassNotFoundException {
209            if (name.equals(NAME)) {
210                if (loaded != null) {
211                    return loaded;
212                }
213                byte[] bytes = Gen.bytesForB();
214                return (loaded = defineClass(name, bytes, 0, bytes.length));
215            } else {
216                return super.findClass(name);
217            }
218        }
219    }
220}
221