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.tools.StandardLocation.CLASS_OUTPUT; 20 21 import android.annotation.UnsupportedAppUsage; 22 23 import com.google.common.base.Joiner; 24 import com.sun.tools.javac.model.JavacElements; 25 import com.sun.tools.javac.tree.JCTree; 26 import com.sun.tools.javac.util.Pair; 27 import com.sun.tools.javac.util.Position; 28 29 import java.io.IOException; 30 import java.io.PrintStream; 31 import java.net.URLEncoder; 32 import java.util.Map; 33 import java.util.Set; 34 import java.util.TreeMap; 35 import java.util.stream.Stream; 36 37 import javax.annotation.processing.AbstractProcessor; 38 import javax.annotation.processing.RoundEnvironment; 39 import javax.annotation.processing.SupportedAnnotationTypes; 40 import javax.lang.model.SourceVersion; 41 import javax.lang.model.element.AnnotationMirror; 42 import javax.lang.model.element.AnnotationValue; 43 import javax.lang.model.element.Element; 44 import javax.lang.model.element.ExecutableElement; 45 import javax.lang.model.element.TypeElement; 46 47 /** 48 * Annotation processor for {@link UnsupportedAppUsage} annotations. 49 * 50 * This processor currently outputs two things: 51 * 1. A greylist.txt containing dex signatures of all annotated elements. 52 * 2. A CSV file with a mapping of dex signatures to corresponding source positions. 53 * 54 * The first will be used at a later stage of the build to add access flags to the dex file. The 55 * second is used for automating updates to the annotations themselves. 56 */ 57 @SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage"}) 58 public class UnsupportedAppUsageProcessor extends AbstractProcessor { 59 60 // Package name for writing output. Output will be written to the "class output" location within 61 // this package. 62 private static final String PACKAGE = "unsupportedappusage"; 63 private static final String INDEX_CSV = "unsupportedappusage_index.csv"; 64 65 @Override getSupportedSourceVersion()66 public SourceVersion getSupportedSourceVersion() { 67 return SourceVersion.latest(); 68 } 69 70 /** 71 * Write the contents of a stream to a text file, with one line per item. 72 */ writeToFile(String name, String headerLine, Stream<?> contents)73 private void writeToFile(String name, 74 String headerLine, 75 Stream<?> contents) throws IOException { 76 PrintStream out = new PrintStream(processingEnv.getFiler().createResource( 77 CLASS_OUTPUT, 78 PACKAGE, 79 name) 80 .openOutputStream()); 81 out.println(headerLine); 82 contents.forEach(o -> out.println(o)); 83 if (out.checkError()) { 84 throw new IOException("Error when writing to " + name); 85 } 86 out.close(); 87 } 88 89 /** 90 * Find the annotation mirror for the @UnsupportedAppUsage annotation on the given element. 91 */ getUnsupportedAppUsageAnnotationMirror(Element e)92 private AnnotationMirror getUnsupportedAppUsageAnnotationMirror(Element e) { 93 for (AnnotationMirror m : e.getAnnotationMirrors()) { 94 TypeElement type = (TypeElement) m.getAnnotationType().asElement(); 95 if (type.getQualifiedName().toString().equals( 96 UnsupportedAppUsage.class.getCanonicalName())) { 97 return m; 98 } 99 } 100 return null; 101 } 102 103 /** 104 * Returns a CSV header line for the columns returned by 105 * {@link #getAnnotationIndex(String, Element)}. 106 */ getCsvHeaders()107 private String getCsvHeaders() { 108 return Joiner.on(',').join( 109 "signature", 110 "file", 111 "startline", 112 "startcol", 113 "endline", 114 "endcol", 115 "properties" 116 ); 117 } 118 encodeAnnotationProperties(AnnotationMirror annotation)119 private String encodeAnnotationProperties(AnnotationMirror annotation) { 120 StringBuilder sb = new StringBuilder(); 121 for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e 122 : annotation.getElementValues().entrySet()) { 123 if (sb.length() > 0) { 124 sb.append("&"); 125 } 126 sb.append(e.getKey().getSimpleName()) 127 .append("=") 128 .append(URLEncoder.encode(e.getValue().toString())); 129 } 130 return sb.toString(); 131 } 132 133 /** 134 * Maps an annotated element to the source position of the @UnsupportedAppUsage annotation 135 * attached to it. It returns CSV in the format: 136 * dex-signature,filename,start-line,start-col,end-line,end-col 137 * 138 * The positions refer to the annotation itself, *not* the annotated member. This can therefore 139 * be used to read just the annotation from the file, and to perform in-place edits on it. 140 * 141 * @param signature the dex signature for the element. 142 * @param annotatedElement The annotated element 143 * @return A single line of CSV text 144 */ getAnnotationIndex(String signature, Element annotatedElement)145 private String getAnnotationIndex(String signature, Element annotatedElement) { 146 JavacElements javacElem = (JavacElements) processingEnv.getElementUtils(); 147 AnnotationMirror unsupportedAppUsage = 148 getUnsupportedAppUsageAnnotationMirror(annotatedElement); 149 Pair<JCTree, JCTree.JCCompilationUnit> pair = 150 javacElem.getTreeAndTopLevel(annotatedElement, unsupportedAppUsage, null); 151 Position.LineMap lines = pair.snd.lineMap; 152 return Joiner.on(",").join( 153 signature, 154 pair.snd.getSourceFile().getName(), 155 lines.getLineNumber(pair.fst.pos().getStartPosition()), 156 lines.getColumnNumber(pair.fst.pos().getStartPosition()), 157 lines.getLineNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)), 158 lines.getColumnNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)), 159 encodeAnnotationProperties(unsupportedAppUsage)); 160 } 161 162 /** 163 * This is the main entry point in the processor, called by the compiler. 164 */ 165 @Override process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)166 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 167 Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith( 168 UnsupportedAppUsage.class); 169 if (annotated.size() == 0) { 170 return true; 171 } 172 // build signatures for each annotated member, and put them in a map of signature to member 173 Map<String, Element> signatureMap = new TreeMap<>(); 174 SignatureBuilder sb = new SignatureBuilder(processingEnv.getMessager()); 175 for (Element e : annotated) { 176 String sig = sb.buildSignature(e); 177 if (sig != null) { 178 signatureMap.put(sig, e); 179 } 180 } 181 try { 182 writeToFile(INDEX_CSV, 183 getCsvHeaders(), 184 signatureMap.entrySet() 185 .stream() 186 .map(e -> getAnnotationIndex(e.getKey() ,e.getValue()))); 187 } catch (IOException e) { 188 throw new RuntimeException("Failed to write output", e); 189 } 190 return true; 191 } 192 } 193