1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.processor.compat.unsupportedappusage; 17 18 import static javax.tools.Diagnostic.Kind.ERROR; 19 20 import com.google.common.collect.ImmutableMap; 21 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.Map; 26 27 import javax.annotation.processing.Messager; 28 import javax.lang.model.element.AnnotationMirror; 29 import javax.lang.model.element.Element; 30 import javax.lang.model.element.ExecutableElement; 31 import javax.lang.model.element.PackageElement; 32 import javax.lang.model.element.TypeElement; 33 import javax.lang.model.element.VariableElement; 34 import javax.lang.model.type.ArrayType; 35 import javax.lang.model.type.DeclaredType; 36 import javax.lang.model.type.TypeKind; 37 import javax.lang.model.type.TypeMirror; 38 import javax.lang.model.type.TypeVariable; 39 import javax.lang.model.util.Types; 40 41 /** 42 * Builds a dex signature for a given method or field. 43 */ 44 final class SignatureConverter { 45 46 private static final Map<TypeKind, String> TYPE_MAP = ImmutableMap.<TypeKind, String>builder() 47 .put(TypeKind.BOOLEAN, "Z") 48 .put(TypeKind.BYTE, "B") 49 .put(TypeKind.CHAR, "C") 50 .put(TypeKind.DOUBLE, "D") 51 .put(TypeKind.FLOAT, "F") 52 .put(TypeKind.INT, "I") 53 .put(TypeKind.LONG, "J") 54 .put(TypeKind.SHORT, "S") 55 .put(TypeKind.VOID, "V") 56 .build(); 57 58 private final Messager messager; 59 SignatureConverter(Messager messager)60 SignatureConverter(Messager messager) { 61 this.messager = messager; 62 } 63 64 /** 65 * Creates the signature for an annotated element. 66 */ getSignature(Types types, TypeElement annotation, Element element)67 String getSignature(Types types, TypeElement annotation, Element element) { 68 try { 69 String signature; 70 switch (element.getKind()) { 71 case METHOD: 72 signature = buildMethodSignature((ExecutableElement) element); 73 break; 74 case CONSTRUCTOR: 75 signature = buildConstructorSignature((ExecutableElement) element); 76 break; 77 case FIELD: 78 signature = buildFieldSignature((VariableElement) element); 79 break; 80 default: 81 return null; 82 } 83 return verifyExpectedSignature(types, signature, element, annotation); 84 } catch (SignatureConverterException problem) { 85 messager.printMessage(ERROR, problem.getMessage(), element); 86 return null; 87 } 88 } 89 90 /** 91 * Returns a list of enclosing elements for the given element, with the package first, and 92 * excluding the element itself. 93 */ getEnclosingElements(Element element)94 private List<Element> getEnclosingElements(Element element) { 95 List<Element> enclosing = new ArrayList<>(); 96 element = element.getEnclosingElement(); // don't include the element itself. 97 while (element != null) { 98 enclosing.add(element); 99 element = element.getEnclosingElement(); 100 } 101 Collections.reverse(enclosing); 102 return enclosing; 103 } 104 105 /** 106 * Get the dex signature for a clazz, in format "Lpackage/name/Outer$Inner;" 107 */ getClassSignature(TypeElement clazz)108 private String getClassSignature(TypeElement clazz) { 109 StringBuilder signature = new StringBuilder("L"); 110 for (Element enclosing : getEnclosingElements(clazz)) { 111 switch (enclosing.getKind()) { 112 case MODULE: 113 // ignore 114 break; 115 case PACKAGE: 116 signature.append(((PackageElement) enclosing) 117 .getQualifiedName() 118 .toString() 119 .replace('.', '/')); 120 signature.append('/'); 121 break; 122 case CLASS: 123 case INTERFACE: 124 signature.append(enclosing.getSimpleName()).append('$'); 125 break; 126 default: 127 throw new IllegalStateException( 128 "Unexpected enclosing element " + enclosing.getKind()); 129 } 130 } 131 return signature 132 .append(clazz.getSimpleName()) 133 .append(";") 134 .toString(); 135 } 136 137 /** 138 * Returns the type signature for a given type. 139 * 140 * <p>For primitive types, a single character. For classes, the class signature. For arrays, a 141 * "[" preceding the component type. 142 */ getTypeSignature(TypeMirror type)143 private String getTypeSignature(TypeMirror type) throws SignatureConverterException { 144 TypeKind kind = type.getKind(); 145 if (TYPE_MAP.containsKey(kind)) { 146 return TYPE_MAP.get(kind); 147 } 148 switch (kind) { 149 case ARRAY: 150 return "[" + getTypeSignature(((ArrayType) type).getComponentType()); 151 case DECLARED: 152 Element declaring = ((DeclaredType) type).asElement(); 153 if (!(declaring instanceof TypeElement)) { 154 throw new SignatureConverterException( 155 "Can't handle declared type of kind " + declaring.getKind()); 156 } 157 return getClassSignature((TypeElement) declaring); 158 case TYPEVAR: 159 TypeVariable typeVar = (TypeVariable)type; 160 if (typeVar.getLowerBound().getKind() != TypeKind.NULL) { 161 return getTypeSignature(typeVar.getLowerBound()); 162 } else if (typeVar.getUpperBound().getKind() != TypeKind.NULL) { 163 return getTypeSignature(typeVar.getUpperBound()); 164 } else { 165 throw new SignatureConverterException("Can't handle typevar with no bound"); 166 } 167 default: 168 throw new SignatureConverterException("Can't handle type of kind " + kind); 169 } 170 } 171 172 /** 173 * Get the signature for an executable, either a method or a constructor. 174 * 175 * @param name "<init>" for constructor, else the method name 176 * @param method The executable element in question. 177 */ getExecutableSignature(CharSequence name, ExecutableElement method)178 private String getExecutableSignature(CharSequence name, ExecutableElement method) 179 throws SignatureConverterException { 180 StringBuilder signature = new StringBuilder(); 181 signature.append(getClassSignature((TypeElement) method.getEnclosingElement())) 182 .append("->") 183 .append(name) 184 .append("("); 185 for (VariableElement param : method.getParameters()) { 186 signature.append(getTypeSignature(param.asType())); 187 } 188 signature 189 .append(")") 190 .append(getTypeSignature(method.getReturnType())); 191 return signature.toString(); 192 } 193 buildMethodSignature(ExecutableElement method)194 private String buildMethodSignature(ExecutableElement method) 195 throws SignatureConverterException { 196 return getExecutableSignature(method.getSimpleName(), method); 197 } 198 buildConstructorSignature(ExecutableElement constructor)199 private String buildConstructorSignature(ExecutableElement constructor) 200 throws SignatureConverterException { 201 return getExecutableSignature("<init>", constructor); 202 } 203 buildFieldSignature(VariableElement field)204 private String buildFieldSignature(VariableElement field) throws SignatureConverterException { 205 return new StringBuilder() 206 .append(getClassSignature((TypeElement) field.getEnclosingElement())) 207 .append("->") 208 .append(field.getSimpleName()) 209 .append(":") 210 .append(getTypeSignature(field.asType())) 211 .toString(); 212 } 213 214 /** If we have an expected signature on the annotation, warn if it doesn't match. */ verifyExpectedSignature(Types types, String signature, Element element, TypeElement annotation)215 private String verifyExpectedSignature(Types types, String signature, Element element, 216 TypeElement annotation) throws SignatureConverterException { 217 AnnotationMirror annotationMirror = null; 218 for (AnnotationMirror mirror : element.getAnnotationMirrors()) { 219 if (types.isSameType(annotation.asType(), mirror.getAnnotationType())) { 220 annotationMirror = mirror; 221 break; 222 } 223 } 224 if (annotationMirror == null) { 225 throw new SignatureConverterException( 226 "Element doesn't have any UnsupportedAppUsage annotation"); 227 } 228 String expectedSignature = annotationMirror.getElementValues().entrySet().stream() 229 .filter(e -> e.getKey().getSimpleName().contentEquals("expectedSignature")) 230 .map(e -> (String) e.getValue().getValue()) 231 .findAny() 232 .orElse(signature); 233 if (!signature.equals(expectedSignature)) { 234 throw new SignatureConverterException(String.format( 235 "Expected signature doesn't match generated signature.\n" 236 + " Expected: %s\n Generated: %s", 237 expectedSignature, 238 signature)); 239 } 240 return signature; 241 } 242 243 /** 244 * Exception used internally when we can't build a signature. 245 */ 246 private static class SignatureConverterException extends Exception { SignatureConverterException(String message)247 SignatureConverterException(String message) { 248 super(message); 249 } 250 } 251 } 252