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 17 package android.processor.unsupportedappusage; 18 19 import static javax.lang.model.element.ElementKind.PACKAGE; 20 import static javax.tools.Diagnostic.Kind.ERROR; 21 import static javax.tools.Diagnostic.Kind.WARNING; 22 23 import android.annotation.UnsupportedAppUsage; 24 25 import com.google.common.base.Strings; 26 import com.google.common.collect.ImmutableMap; 27 import com.sun.tools.javac.code.Type; 28 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.List; 32 import java.util.Map; 33 34 import javax.annotation.processing.Messager; 35 import javax.lang.model.element.Element; 36 import javax.lang.model.element.ExecutableElement; 37 import javax.lang.model.element.PackageElement; 38 import javax.lang.model.element.TypeElement; 39 import javax.lang.model.element.VariableElement; 40 import javax.lang.model.type.ArrayType; 41 import javax.lang.model.type.DeclaredType; 42 import javax.lang.model.type.TypeKind; 43 import javax.lang.model.type.TypeMirror; 44 45 /** 46 * Builds a dex signature for a given method or field. 47 */ 48 public class SignatureBuilder { 49 50 private static final Map<TypeKind, String> TYPE_MAP = ImmutableMap.<TypeKind, String>builder() 51 .put(TypeKind.BOOLEAN, "Z") 52 .put(TypeKind.BYTE, "B") 53 .put(TypeKind.CHAR, "C") 54 .put(TypeKind.DOUBLE, "D") 55 .put(TypeKind.FLOAT, "F") 56 .put(TypeKind.INT, "I") 57 .put(TypeKind.LONG, "J") 58 .put(TypeKind.SHORT, "S") 59 .put(TypeKind.VOID, "V") 60 .build(); 61 62 private final Messager mMessager; 63 64 /** 65 * Exception used internally when we can't build a signature. Whenever this is thrown, an error 66 * will also be written to the Messager. 67 */ 68 private class SignatureBuilderException extends Exception { SignatureBuilderException(String message)69 public SignatureBuilderException(String message) { 70 super(message); 71 } report(Element offendingElement)72 public void report(Element offendingElement) { 73 mMessager.printMessage(ERROR, getMessage(), offendingElement); 74 } 75 } 76 SignatureBuilder(Messager messager)77 public SignatureBuilder(Messager messager) { 78 mMessager = messager; 79 } 80 81 /** 82 * Returns a list of enclosing elements for the given element, with the package first, and 83 * excluding the element itself. 84 */ getEnclosingElements(Element e)85 private List<Element> getEnclosingElements(Element e) { 86 List<Element> enclosing = new ArrayList<>(); 87 e = e.getEnclosingElement(); // don't include the element itself. 88 while (e != null) { 89 enclosing.add(e); 90 e = e.getEnclosingElement(); 91 } 92 Collections.reverse(enclosing); 93 return enclosing; 94 } 95 96 /** 97 * Get the dex signature for a clazz, in format "Lpackage/name/Outer$Inner;" 98 */ getClassSignature(TypeElement clazz)99 private String getClassSignature(TypeElement clazz) { 100 StringBuilder sb = new StringBuilder("L"); 101 for (Element enclosing : getEnclosingElements(clazz)) { 102 if (enclosing.getKind() == PACKAGE) { 103 sb.append(((PackageElement) enclosing) 104 .getQualifiedName() 105 .toString() 106 .replace('.', '/')); 107 sb.append('/'); 108 } else { 109 sb.append(enclosing.getSimpleName()).append('$'); 110 } 111 112 } 113 return sb 114 .append(clazz.getSimpleName()) 115 .append(";") 116 .toString(); 117 } 118 119 /** 120 * Returns the type signature for a given type. For primitive types, a single character. 121 * For classes, the class signature. For arrays, a "[" preceeding the component type. 122 */ getTypeSignature(TypeMirror type)123 private String getTypeSignature(TypeMirror type) throws SignatureBuilderException { 124 String sig = TYPE_MAP.get(type.getKind()); 125 if (sig != null) { 126 return sig; 127 } 128 switch (type.getKind()) { 129 case ARRAY: 130 return "[" + getTypeSignature(((ArrayType) type).getComponentType()); 131 case DECLARED: 132 Element declaring = ((DeclaredType) type).asElement(); 133 if (!(declaring instanceof TypeElement)) { 134 throw new SignatureBuilderException( 135 "Can't handle declared type of kind " + declaring.getKind()); 136 } 137 return getClassSignature((TypeElement) declaring); 138 case TYPEVAR: 139 Type.TypeVar typeVar = (Type.TypeVar) type; 140 if (typeVar.getLowerBound().getKind() != TypeKind.NULL) { 141 return getTypeSignature(typeVar.getLowerBound()); 142 } else if (typeVar.getUpperBound().getKind() != TypeKind.NULL) { 143 return getTypeSignature(typeVar.getUpperBound()); 144 } else { 145 throw new SignatureBuilderException("Can't handle typevar with no bound"); 146 } 147 148 default: 149 throw new SignatureBuilderException("Can't handle type of kind " + type.getKind()); 150 } 151 } 152 153 /** 154 * Get the signature for an executable, either a method or a constructor. 155 * 156 * @param name "<init>" for constructor, else the method name 157 * @param method The executable element in question. 158 */ getExecutableSignature(CharSequence name, ExecutableElement method)159 private String getExecutableSignature(CharSequence name, ExecutableElement method) 160 throws SignatureBuilderException { 161 StringBuilder sig = new StringBuilder(); 162 sig.append(getClassSignature((TypeElement) method.getEnclosingElement())) 163 .append("->") 164 .append(name) 165 .append("("); 166 for (VariableElement param : method.getParameters()) { 167 sig.append(getTypeSignature(param.asType())); 168 } 169 sig.append(")") 170 .append(getTypeSignature(method.getReturnType())); 171 return sig.toString(); 172 } 173 buildMethodSignature(ExecutableElement method)174 private String buildMethodSignature(ExecutableElement method) throws SignatureBuilderException { 175 return getExecutableSignature(method.getSimpleName(), method); 176 } 177 buildConstructorSignature(ExecutableElement cons)178 private String buildConstructorSignature(ExecutableElement cons) 179 throws SignatureBuilderException { 180 return getExecutableSignature("<init>", cons); 181 } 182 buildFieldSignature(VariableElement field)183 private String buildFieldSignature(VariableElement field) throws SignatureBuilderException { 184 StringBuilder sig = new StringBuilder(); 185 sig.append(getClassSignature((TypeElement) field.getEnclosingElement())) 186 .append("->") 187 .append(field.getSimpleName()) 188 .append(":") 189 .append(getTypeSignature(field.asType())) 190 ; 191 return sig.toString(); 192 } 193 buildSignature(Element element)194 public String buildSignature(Element element) { 195 UnsupportedAppUsage uba = element.getAnnotation(UnsupportedAppUsage.class); 196 try { 197 String signature; 198 switch (element.getKind()) { 199 case METHOD: 200 signature = buildMethodSignature((ExecutableElement) element); 201 break; 202 case CONSTRUCTOR: 203 signature = buildConstructorSignature((ExecutableElement) element); 204 break; 205 case FIELD: 206 signature = buildFieldSignature((VariableElement) element); 207 break; 208 default: 209 return null; 210 } 211 // if we have an expected signature on the annotation, warn if it doesn't match. 212 if (!Strings.isNullOrEmpty(uba.expectedSignature())) { 213 if (!signature.equals(uba.expectedSignature())) { 214 mMessager.printMessage( 215 WARNING, 216 String.format("Expected signature doesn't match generated signature.\n" 217 + " Expected: %s\n Generated: %s", 218 uba.expectedSignature(), signature), 219 element); 220 } 221 } 222 return signature; 223 } catch (SignatureBuilderException problem) { 224 problem.report(element); 225 return null; 226 } 227 } 228 } 229