001/* 002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. 008 * 009 * This code is distributed in the hope that it will be useful, but WITHOUT 010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 011 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 012 * version 2 for more details (a copy is included in the LICENSE file that 013 * accompanied this code). 014 * 015 * You should have received a copy of the GNU General Public License version 016 * 2 along with this work; if not, write to the Free Software Foundation, 017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 018 * 019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 020 * or visit www.oracle.com if you need additional information or have any 021 * questions. 022 */ 023package com.oracle.graal.replacements.verifier; 024 025import java.lang.annotation.*; 026import java.util.*; 027 028import javax.annotation.processing.*; 029import javax.lang.model.element.*; 030import javax.lang.model.type.*; 031import javax.lang.model.util.*; 032import javax.tools.Diagnostic.Kind; 033 034import com.oracle.graal.api.replacements.*; 035 036public final class MethodSubstitutionVerifier extends AbstractVerifier { 037 038 private static final boolean DEBUG = false; 039 040 private static final String ORIGINAL_METHOD_NAME = "value"; 041 private static final String ORIGINAL_IS_STATIC = "isStatic"; 042 private static final String ORIGINAL_SIGNATURE = "signature"; 043 044 private static final String ORIGINAL_METHOD_NAME_DEFAULT = ""; 045 private static final String ORIGINAL_SIGNATURE_DEFAULT = ""; 046 047 public MethodSubstitutionVerifier(ProcessingEnvironment env) { 048 super(env); 049 } 050 051 @Override 052 public Class<? extends Annotation> getAnnotationClass() { 053 return MethodSubstitution.class; 054 } 055 056 @SuppressWarnings("unused") 057 @Override 058 public void verify(Element element, AnnotationMirror annotation) { 059 if (element.getKind() != ElementKind.METHOD) { 060 assert false : "Element is guaranteed to be a method."; 061 return; 062 } 063 ExecutableElement substitutionMethod = (ExecutableElement) element; 064 TypeElement substitutionType = findEnclosingClass(substitutionMethod); 065 assert substitutionType != null; 066 067 AnnotationMirror substitutionClassAnnotation = VerifierAnnotationProcessor.findAnnotationMirror(env, substitutionType.getAnnotationMirrors(), ClassSubstitution.class); 068 if (substitutionClassAnnotation == null) { 069 env.getMessager().printMessage(Kind.ERROR, String.format("A @%s annotation is required on the enclosing class.", ClassSubstitution.class.getSimpleName()), element, annotation); 070 return; 071 } 072 boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionClassAnnotation, "optional")); 073 if (optional) { 074 return; 075 } 076 077 TypeElement originalType = ClassSubstitutionVerifier.resolveOriginalType(env, substitutionType, substitutionClassAnnotation); 078 if (originalType == null) { 079 env.getMessager().printMessage(Kind.ERROR, String.format("The @%s annotation is invalid on the enclosing class.", ClassSubstitution.class.getSimpleName()), element, annotation); 080 return; 081 } 082 083 if (!substitutionMethod.getModifiers().contains(Modifier.STATIC)) { 084 env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must be static.", MethodSubstitution.class.getSimpleName()), element, annotation); 085 } 086 087 if (substitutionMethod.getModifiers().contains(Modifier.ABSTRACT) || substitutionMethod.getModifiers().contains(Modifier.NATIVE)) { 088 env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must not be native or abstract.", MethodSubstitution.class.getSimpleName()), element, annotation); 089 } 090 091 String originalName = originalName(substitutionMethod, annotation); 092 boolean isStatic = resolveAnnotationValue(Boolean.class, findAnnotationValue(annotation, ORIGINAL_IS_STATIC)); 093 TypeMirror[] originalSignature = originalSignature(originalType, substitutionMethod, annotation, isStatic); 094 if (originalSignature == null) { 095 return; 096 } 097 ExecutableElement originalMethod = originalMethod(substitutionMethod, annotation, originalType, originalName, originalSignature, isStatic); 098 if (DEBUG && originalMethod != null) { 099 env.getMessager().printMessage(Kind.NOTE, String.format("Found original method %s in type %s.", originalMethod, findEnclosingClass(originalMethod))); 100 } 101 } 102 103 private TypeMirror[] originalSignature(TypeElement originalType, ExecutableElement method, AnnotationMirror annotation, boolean isStatic) { 104 AnnotationValue signatureValue = findAnnotationValue(annotation, ORIGINAL_SIGNATURE); 105 String signatureString = resolveAnnotationValue(String.class, signatureValue); 106 List<TypeMirror> parameters = new ArrayList<>(); 107 if (signatureString.equals(ORIGINAL_SIGNATURE_DEFAULT)) { 108 for (int i = 0; i < method.getParameters().size(); i++) { 109 parameters.add(method.getParameters().get(i).asType()); 110 } 111 if (!isStatic) { 112 if (parameters.isEmpty()) { 113 env.getMessager().printMessage(Kind.ERROR, "Method signature must be a static method with the 'this' object as its first parameter", method, annotation); 114 return null; 115 } else { 116 TypeMirror thisParam = parameters.remove(0); 117 if (!isSubtype(originalType.asType(), thisParam)) { 118 Name thisName = method.getParameters().get(0).getSimpleName(); 119 env.getMessager().printMessage(Kind.ERROR, String.format("The type of %s must assignable from %s", thisName, originalType), method, annotation); 120 } 121 } 122 } 123 parameters.add(0, method.getReturnType()); 124 } else { 125 try { 126 APHotSpotSignature signature = new APHotSpotSignature(signatureString); 127 parameters.add(signature.getReturnType(env)); 128 for (int i = 0; i < signature.getParameterCount(false); i++) { 129 parameters.add(signature.getParameterType(env, i)); 130 } 131 } catch (Exception e) { 132 /* 133 * That's not good practice and should be changed after APHotSpotSignature has 134 * received a cleanup. 135 */ 136 env.getMessager().printMessage(Kind.ERROR, String.format("Parsing the signature failed: %s", e.getMessage() != null ? e.getMessage() : e.toString()), method, annotation, 137 signatureValue); 138 return null; 139 } 140 } 141 return parameters.toArray(new TypeMirror[parameters.size()]); 142 } 143 144 private static String originalName(ExecutableElement substituteMethod, AnnotationMirror substitution) { 145 String originalMethodName = resolveAnnotationValue(String.class, findAnnotationValue(substitution, ORIGINAL_METHOD_NAME)); 146 if (originalMethodName.equals(ORIGINAL_METHOD_NAME_DEFAULT)) { 147 originalMethodName = substituteMethod.getSimpleName().toString(); 148 } 149 return originalMethodName; 150 } 151 152 private ExecutableElement originalMethod(ExecutableElement substitutionMethod, AnnotationMirror substitutionAnnotation, TypeElement originalType, String originalName, 153 TypeMirror[] originalSignature, boolean isStatic) { 154 TypeMirror signatureReturnType = originalSignature[0]; 155 TypeMirror[] signatureParameters = Arrays.copyOfRange(originalSignature, 1, originalSignature.length); 156 List<ExecutableElement> searchElements; 157 if (originalName.equals("<init>")) { 158 searchElements = ElementFilter.constructorsIn(originalType.getEnclosedElements()); 159 } else { 160 searchElements = ElementFilter.methodsIn(originalType.getEnclosedElements()); 161 } 162 163 ExecutableElement originalMethod = null; 164 outer: for (ExecutableElement searchElement : searchElements) { 165 if (searchElement.getSimpleName().toString().equals(originalName) && searchElement.getParameters().size() == signatureParameters.length) { 166 for (int i = 0; i < signatureParameters.length; i++) { 167 VariableElement parameter = searchElement.getParameters().get(i); 168 if (!isTypeCompatible(parameter.asType(), signatureParameters[i])) { 169 continue outer; 170 } 171 } 172 originalMethod = searchElement; 173 break; 174 } 175 } 176 if (originalMethod == null) { 177 boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionAnnotation, "optional")); 178 if (!optional) { 179 env.getMessager().printMessage(Kind.ERROR, String.format("Could not find the original method with name '%s' and parameters '%s'.", originalName, Arrays.toString(signatureParameters)), 180 substitutionMethod, substitutionAnnotation); 181 } 182 return null; 183 } 184 185 if (originalMethod.getModifiers().contains(Modifier.STATIC) != isStatic) { 186 boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionAnnotation, "optional")); 187 if (!optional) { 188 env.getMessager().printMessage(Kind.ERROR, String.format("The %s element must be set to %s.", ORIGINAL_IS_STATIC, !isStatic), substitutionMethod, substitutionAnnotation); 189 } 190 return null; 191 } 192 193 if (!isTypeCompatible(originalMethod.getReturnType(), signatureReturnType)) { 194 env.getMessager().printMessage( 195 Kind.ERROR, 196 String.format("The return type of the substitution method '%s' must match with the return type of the original method '%s'.", signatureReturnType, 197 originalMethod.getReturnType()), substitutionMethod, substitutionAnnotation); 198 return null; 199 } 200 201 return originalMethod; 202 } 203 204 private boolean isTypeCompatible(TypeMirror originalType, TypeMirror substitutionType) { 205 TypeMirror original = originalType; 206 TypeMirror substitution = substitutionType; 207 if (needsErasure(original)) { 208 original = env.getTypeUtils().erasure(original); 209 } 210 if (needsErasure(substitution)) { 211 substitution = env.getTypeUtils().erasure(substitution); 212 } 213 return env.getTypeUtils().isSameType(original, substitution); 214 } 215 216 /** 217 * Tests whether one type is a subtype of another. Any type is considered to be a subtype of 218 * itself. 219 * 220 * @param t1 the first type 221 * @param t2 the second type 222 * @return {@code true} if and only if the first type is a subtype of the second 223 */ 224 private boolean isSubtype(TypeMirror t1, TypeMirror t2) { 225 TypeMirror t1Erased = t1; 226 TypeMirror t2Erased = t2; 227 if (needsErasure(t1Erased)) { 228 t1Erased = env.getTypeUtils().erasure(t1Erased); 229 } 230 if (needsErasure(t2Erased)) { 231 t2Erased = env.getTypeUtils().erasure(t2Erased); 232 } 233 return env.getTypeUtils().isSubtype(t1Erased, t2Erased); 234 } 235 236 private static boolean needsErasure(TypeMirror typeMirror) { 237 return typeMirror.getKind() != TypeKind.NONE && typeMirror.getKind() != TypeKind.VOID && !typeMirror.getKind().isPrimitive() && typeMirror.getKind() != TypeKind.OTHER && 238 typeMirror.getKind() != TypeKind.NULL; 239 } 240 241 private static TypeElement findEnclosingClass(Element element) { 242 if (element.getKind().isClass()) { 243 return (TypeElement) element; 244 } 245 246 Element enclosing = element.getEnclosingElement(); 247 while (enclosing != null && enclosing.getKind() != ElementKind.PACKAGE) { 248 if (enclosing.getKind().isClass()) { 249 return (TypeElement) enclosing; 250 } 251 enclosing = enclosing.getEnclosingElement(); 252 } 253 return null; 254 } 255 256}