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