MethodSubstitutionVerifier.java revision 12651:6ef01bd40ce2
1/*
2 * Copyright (c) 2013, 2015, 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.replacements.verifier;
24
25import java.lang.annotation.Annotation;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.List;
29
30import javax.annotation.processing.ProcessingEnvironment;
31import javax.lang.model.element.AnnotationMirror;
32import javax.lang.model.element.AnnotationValue;
33import javax.lang.model.element.Element;
34import javax.lang.model.element.ElementKind;
35import javax.lang.model.element.ExecutableElement;
36import javax.lang.model.element.Modifier;
37import javax.lang.model.element.Name;
38import javax.lang.model.element.TypeElement;
39import javax.lang.model.element.VariableElement;
40import javax.lang.model.type.TypeKind;
41import javax.lang.model.type.TypeMirror;
42import javax.lang.model.util.ElementFilter;
43import javax.tools.Diagnostic.Kind;
44
45import org.graalvm.compiler.api.replacements.ClassSubstitution;
46import org.graalvm.compiler.api.replacements.MethodSubstitution;
47
48public final class MethodSubstitutionVerifier extends AbstractVerifier {
49
50    private static final boolean DEBUG = false;
51
52    private static final String ORIGINAL_METHOD_NAME = "value";
53    private static final String ORIGINAL_IS_STATIC = "isStatic";
54    private static final String ORIGINAL_SIGNATURE = "signature";
55
56    private static final String ORIGINAL_METHOD_NAME_DEFAULT = "";
57    private static final String ORIGINAL_SIGNATURE_DEFAULT = "";
58
59    public MethodSubstitutionVerifier(ProcessingEnvironment env) {
60        super(env);
61    }
62
63    @Override
64    public Class<? extends Annotation> getAnnotationClass() {
65        return MethodSubstitution.class;
66    }
67
68    @SuppressWarnings("unused")
69    @Override
70    public void verify(Element element, AnnotationMirror annotation, PluginGenerator generator) {
71        if (element.getKind() != ElementKind.METHOD) {
72            assert false : "Element is guaranteed to be a method.";
73            return;
74        }
75        ExecutableElement substitutionMethod = (ExecutableElement) element;
76        TypeElement substitutionType = findEnclosingClass(substitutionMethod);
77        assert substitutionType != null;
78
79        AnnotationMirror substitutionClassAnnotation = VerifierAnnotationProcessor.findAnnotationMirror(env, substitutionType.getAnnotationMirrors(), ClassSubstitution.class);
80        if (substitutionClassAnnotation == null) {
81            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s annotation is required on the enclosing class.", ClassSubstitution.class.getSimpleName()), element, annotation);
82            return;
83        }
84        boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionClassAnnotation, "optional"));
85        if (optional) {
86            return;
87        }
88
89        TypeElement originalType = ClassSubstitutionVerifier.resolveOriginalType(env, substitutionType, substitutionClassAnnotation);
90        if (originalType == null) {
91            env.getMessager().printMessage(Kind.ERROR, String.format("The @%s annotation is invalid on the enclosing class.", ClassSubstitution.class.getSimpleName()), element, annotation);
92            return;
93        }
94
95        if (!substitutionMethod.getModifiers().contains(Modifier.STATIC)) {
96            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must be static.", MethodSubstitution.class.getSimpleName()), element, annotation);
97        }
98
99        if (substitutionMethod.getModifiers().contains(Modifier.ABSTRACT) || substitutionMethod.getModifiers().contains(Modifier.NATIVE)) {
100            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must not be native or abstract.", MethodSubstitution.class.getSimpleName()), element, annotation);
101        }
102
103        String originalName = originalName(substitutionMethod, annotation);
104        boolean isStatic = resolveAnnotationValue(Boolean.class, findAnnotationValue(annotation, ORIGINAL_IS_STATIC));
105        TypeMirror[] originalSignature = originalSignature(originalType, substitutionMethod, annotation, isStatic);
106        if (originalSignature == null) {
107            return;
108        }
109        ExecutableElement originalMethod = originalMethod(substitutionMethod, annotation, originalType, originalName, originalSignature, isStatic);
110        if (DEBUG && originalMethod != null) {
111            env.getMessager().printMessage(Kind.NOTE, String.format("Found original method %s in type %s.", originalMethod, findEnclosingClass(originalMethod)));
112        }
113    }
114
115    private TypeMirror[] originalSignature(TypeElement originalType, ExecutableElement method, AnnotationMirror annotation, boolean isStatic) {
116        AnnotationValue signatureValue = findAnnotationValue(annotation, ORIGINAL_SIGNATURE);
117        String signatureString = resolveAnnotationValue(String.class, signatureValue);
118        List<TypeMirror> parameters = new ArrayList<>();
119        if (signatureString.equals(ORIGINAL_SIGNATURE_DEFAULT)) {
120            for (int i = 0; i < method.getParameters().size(); i++) {
121                parameters.add(method.getParameters().get(i).asType());
122            }
123            if (!isStatic) {
124                if (parameters.isEmpty()) {
125                    env.getMessager().printMessage(Kind.ERROR, "Method signature must be a static method with the 'this' object as its first parameter", method, annotation);
126                    return null;
127                } else {
128                    TypeMirror thisParam = parameters.remove(0);
129                    if (!isSubtype(originalType.asType(), thisParam)) {
130                        Name thisName = method.getParameters().get(0).getSimpleName();
131                        env.getMessager().printMessage(Kind.ERROR, String.format("The type of %s must assignable from %s", thisName, originalType), method, annotation);
132                    }
133                }
134            }
135            parameters.add(0, method.getReturnType());
136        } else {
137            try {
138                APHotSpotSignature signature = new APHotSpotSignature(signatureString);
139                parameters.add(signature.getReturnType(env));
140                for (int i = 0; i < signature.getParameterCount(false); i++) {
141                    parameters.add(signature.getParameterType(env, i));
142                }
143            } catch (Exception e) {
144                /*
145                 * That's not good practice and should be changed after APHotSpotSignature has
146                 * received a cleanup.
147                 */
148                env.getMessager().printMessage(Kind.ERROR, String.format("Parsing the signature failed: %s", e.getMessage() != null ? e.getMessage() : e.toString()), method, annotation,
149                                signatureValue);
150                return null;
151            }
152        }
153        return parameters.toArray(new TypeMirror[parameters.size()]);
154    }
155
156    private static String originalName(ExecutableElement substituteMethod, AnnotationMirror substitution) {
157        String originalMethodName = resolveAnnotationValue(String.class, findAnnotationValue(substitution, ORIGINAL_METHOD_NAME));
158        if (originalMethodName.equals(ORIGINAL_METHOD_NAME_DEFAULT)) {
159            originalMethodName = substituteMethod.getSimpleName().toString();
160        }
161        return originalMethodName;
162    }
163
164    private ExecutableElement originalMethod(ExecutableElement substitutionMethod, AnnotationMirror substitutionAnnotation, TypeElement originalType, String originalName,
165                    TypeMirror[] originalSignature, boolean isStatic) {
166        TypeMirror signatureReturnType = originalSignature[0];
167        TypeMirror[] signatureParameters = Arrays.copyOfRange(originalSignature, 1, originalSignature.length);
168        List<ExecutableElement> searchElements;
169        if (originalName.equals("<init>")) {
170            searchElements = ElementFilter.constructorsIn(originalType.getEnclosedElements());
171        } else {
172            searchElements = ElementFilter.methodsIn(originalType.getEnclosedElements());
173        }
174
175        ExecutableElement originalMethod = null;
176        outer: for (ExecutableElement searchElement : searchElements) {
177            if (searchElement.getSimpleName().toString().equals(originalName) && searchElement.getParameters().size() == signatureParameters.length) {
178                for (int i = 0; i < signatureParameters.length; i++) {
179                    VariableElement parameter = searchElement.getParameters().get(i);
180                    if (!isTypeCompatible(parameter.asType(), signatureParameters[i])) {
181                        continue outer;
182                    }
183                }
184                originalMethod = searchElement;
185                break;
186            }
187        }
188        if (originalMethod == null) {
189            boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionAnnotation, "optional"));
190            if (!optional) {
191                env.getMessager().printMessage(Kind.ERROR, String.format("Could not find the original method with name '%s' and parameters '%s'.", originalName, Arrays.toString(signatureParameters)),
192                                substitutionMethod, substitutionAnnotation);
193            }
194            return null;
195        }
196
197        if (originalMethod.getModifiers().contains(Modifier.STATIC) != isStatic) {
198            boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionAnnotation, "optional"));
199            if (!optional) {
200                env.getMessager().printMessage(Kind.ERROR, String.format("The %s element must be set to %s.", ORIGINAL_IS_STATIC, !isStatic), substitutionMethod, substitutionAnnotation);
201            }
202            return null;
203        }
204
205        if (!isTypeCompatible(originalMethod.getReturnType(), signatureReturnType)) {
206            env.getMessager().printMessage(
207                            Kind.ERROR,
208                            String.format("The return type of the substitution method '%s' must match with the return type of the original method '%s'.", signatureReturnType,
209                                            originalMethod.getReturnType()),
210                            substitutionMethod, substitutionAnnotation);
211            return null;
212        }
213
214        return originalMethod;
215    }
216
217    private boolean isTypeCompatible(TypeMirror originalType, TypeMirror substitutionType) {
218        TypeMirror original = originalType;
219        TypeMirror substitution = substitutionType;
220        if (needsErasure(original)) {
221            original = env.getTypeUtils().erasure(original);
222        }
223        if (needsErasure(substitution)) {
224            substitution = env.getTypeUtils().erasure(substitution);
225        }
226        return env.getTypeUtils().isSameType(original, substitution);
227    }
228
229    /**
230     * Tests whether one type is a subtype of another. Any type is considered to be a subtype of
231     * itself.
232     *
233     * @param t1 the first type
234     * @param t2 the second type
235     * @return {@code true} if and only if the first type is a subtype of the second
236     */
237    private boolean isSubtype(TypeMirror t1, TypeMirror t2) {
238        TypeMirror t1Erased = t1;
239        TypeMirror t2Erased = t2;
240        if (needsErasure(t1Erased)) {
241            t1Erased = env.getTypeUtils().erasure(t1Erased);
242        }
243        if (needsErasure(t2Erased)) {
244            t2Erased = env.getTypeUtils().erasure(t2Erased);
245        }
246        return env.getTypeUtils().isSubtype(t1Erased, t2Erased);
247    }
248
249    private static boolean needsErasure(TypeMirror typeMirror) {
250        return typeMirror.getKind() != TypeKind.NONE && typeMirror.getKind() != TypeKind.VOID && !typeMirror.getKind().isPrimitive() && typeMirror.getKind() != TypeKind.OTHER &&
251                        typeMirror.getKind() != TypeKind.NULL;
252    }
253
254    private static TypeElement findEnclosingClass(Element element) {
255        if (element.getKind().isClass()) {
256            return (TypeElement) element;
257        }
258
259        Element enclosing = element.getEnclosingElement();
260        while (enclosing != null && enclosing.getKind() != ElementKind.PACKAGE) {
261            if (enclosing.getKind().isClass()) {
262                return (TypeElement) enclosing;
263            }
264            enclosing = enclosing.getEnclosingElement();
265        }
266        return null;
267    }
268
269}
270