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