1/*
2 * Copyright (c) 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.bytecode;
24
25import static org.graalvm.compiler.bytecode.Bytecodes.ATHROW;
26import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
27import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
28import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
29import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
30
31import java.lang.annotation.Annotation;
32
33import jdk.vm.ci.meta.ConstantPool;
34import jdk.vm.ci.meta.ResolvedJavaMethod;
35
36/**
37 * Utilities for working around the absence of method annotations and parameter annotations on
38 * bridge methods where the bridged methods have method annotations or parameter annotations. Not
39 * all Java compilers copy method annotations and parameter annotations to bridge methods.
40 *
41 * @see <a href="http://bugs.java.com/view_bug.do?bug_id=6695379">6695379</a>
42 * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=495396">495396</a>
43 * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=427745">427745</a>
44 */
45public class BridgeMethodUtils {
46
47    /**
48     * Gets the method bridged to by a {@linkplain ResolvedJavaMethod#isBridge() bridge} method. The
49     * value returned is the method called by {@code method} that has the same name as
50     * {@code bridge}.
51     *
52     * @param bridge a bridge method
53     * @return the method called by {@code bridge} whose name is the same as
54     *         {@code bridge.getName()}
55     */
56    public static ResolvedJavaMethod getBridgedMethod(ResolvedJavaMethod bridge) {
57        assert bridge.isBridge();
58        Bytecode code = new ResolvedJavaMethodBytecode(bridge);
59        BytecodeStream stream = new BytecodeStream(code.getCode());
60        int opcode = stream.currentBC();
61        ResolvedJavaMethod bridged = null;
62        boolean calledAbstractMethodErrorConstructor = false;
63        while (opcode != Bytecodes.END) {
64            switch (opcode) {
65                case INVOKEVIRTUAL:
66                case INVOKESPECIAL:
67                case INVOKESTATIC:
68                case INVOKEINTERFACE: {
69                    int cpi = stream.readCPI();
70                    ConstantPool cp = code.getConstantPool();
71                    cp.loadReferencedType(cpi, opcode);
72                    ResolvedJavaMethod method = (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode);
73                    if (method.getName().equals(bridge.getName())) {
74                        if (!assertionsEnabled()) {
75                            return method;
76                        }
77                        assert bridged == null || bridged.equals(method) : String.format("Found calls to different methods named %s in bridge method %s%n  callee 1: %s%n  callee 2: %s",
78                                        bridge.getName(), bridge.format("%R %H.%n(%P)"), bridged.format("%R %H.%n(%P)"), method.format("%R %H.%n(%P)"));
79                        bridged = method;
80                    } else if (method.getName().equals("<init>") && method.getDeclaringClass().getName().equals("Ljava/lang/AbstractMethodError;")) {
81                        calledAbstractMethodErrorConstructor = true;
82                    }
83                    break;
84                }
85                case ATHROW: {
86                    if (calledAbstractMethodErrorConstructor) {
87                        // This is a miranda method
88                        return null;
89                    }
90                }
91            }
92            stream.next();
93            opcode = stream.currentBC();
94        }
95        if (bridged == null) {
96            String dis = new BytecodeDisassembler().disassemble(bridge);
97            throw new InternalError(String.format("Couldn't find method bridged by %s:%n%s", bridge.format("%R %H.%n(%P)"), dis));
98        }
99        return bridged;
100    }
101
102    @SuppressWarnings("all")
103    private static boolean assertionsEnabled() {
104        boolean enabled = false;
105        assert enabled = true;
106        return enabled;
107    }
108
109    /**
110     * A helper for {@link ResolvedJavaMethod#getAnnotation(Class)} that handles the absence of
111     * annotations on bridge methods where the bridged method has annotations.
112     */
113    public static <T extends Annotation> T getAnnotation(Class<T> annotationClass, ResolvedJavaMethod method) {
114        T a = method.getAnnotation(annotationClass);
115        if (a == null && method.isBridge()) {
116            ResolvedJavaMethod bridged = getBridgedMethod(method);
117            if (bridged != null) {
118                a = bridged.getAnnotation(annotationClass);
119            }
120        }
121        return a;
122    }
123
124    /**
125     * A helper for {@link ResolvedJavaMethod#getAnnotations()} that handles the absence of
126     * annotations on bridge methods where the bridged method has annotations.
127     */
128    public static Annotation[] getAnnotations(ResolvedJavaMethod method) {
129        Annotation[] a = method.getAnnotations();
130        if (a.length == 0 && method.isBridge()) {
131            ResolvedJavaMethod bridged = getBridgedMethod(method);
132            if (bridged != null) {
133                a = bridged.getAnnotations();
134            }
135        }
136        return a;
137    }
138
139    /**
140     * A helper for {@link ResolvedJavaMethod#getDeclaredAnnotations()} that handles the absence of
141     * annotations on bridge methods where the bridged method has annotations.
142     */
143    public static Annotation[] getDeclaredAnnotations(ResolvedJavaMethod method) {
144        Annotation[] a = method.getAnnotations();
145        if (a.length == 0 && method.isBridge()) {
146            ResolvedJavaMethod bridged = getBridgedMethod(method);
147            if (bridged != null) {
148                a = bridged.getDeclaredAnnotations();
149            }
150        }
151        return a;
152    }
153
154    /**
155     * A helper for {@link ResolvedJavaMethod#getParameterAnnotations()} that handles the absence of
156     * parameter annotations on bridge methods where the bridged method has parameter annotations.
157     */
158    public static Annotation[][] getParameterAnnotations(ResolvedJavaMethod method) {
159        Annotation[][] a = method.getParameterAnnotations();
160        if (a.length == 0 && method.isBridge()) {
161            ResolvedJavaMethod bridged = getBridgedMethod(method);
162            if (bridged != null) {
163                a = bridged.getParameterAnnotations();
164            }
165        }
166        return a;
167    }
168
169    /**
170     * A helper for {@link ResolvedJavaMethod#getParameterAnnotation(Class, int)} that handles the
171     * absence of parameter annotations on bridge methods where the bridged method has parameter
172     * annotations.
173     */
174    public static <T extends Annotation> T getParameterAnnotation(Class<T> annotationClass, int parameterIndex, ResolvedJavaMethod method) {
175        T a = method.getParameterAnnotation(annotationClass, parameterIndex);
176        if (a == null && method.isBridge()) {
177            ResolvedJavaMethod bridged = getBridgedMethod(method);
178            if (bridged != null) {
179                a = bridged.getParameterAnnotation(annotationClass, parameterIndex);
180            }
181        }
182        return a;
183    }
184}
185