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 */ 23 package org.graalvm.compiler.replacements.verifier; 24 25 import java.lang.annotation.Annotation; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.List; 29 30 import javax.annotation.processing.ProcessingEnvironment; 31 import javax.lang.model.element.AnnotationMirror; 32 import javax.lang.model.element.AnnotationValue; 33 import javax.lang.model.element.Element; 34 import javax.lang.model.element.ElementKind; 35 import javax.lang.model.element.ExecutableElement; 36 import javax.lang.model.element.Modifier; 37 import javax.lang.model.element.Name; 38 import javax.lang.model.element.TypeElement; 39 import javax.lang.model.element.VariableElement; 40 import javax.lang.model.type.TypeKind; 41 import javax.lang.model.type.TypeMirror; 42 import javax.lang.model.util.ElementFilter; 43 import javax.tools.Diagnostic.Kind; 44 45 import org.graalvm.compiler.api.replacements.ClassSubstitution; 46 import org.graalvm.compiler.api.replacements.MethodSubstitution; 47 48 public 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 }