Merge "Processor for @UnsupportedAppUsage annotations."
am: e5440456ca

Change-Id: I5e4d53b92c20bf3c34248c6cf4798ab4ba34af7c
diff --git a/Android.bp b/Android.bp
index 70438d2..dda318b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,8 +25,8 @@
 //
 // READ ME: ########################################################
 
-java_library {
-    name: "framework",
+java_defaults {
+    name: "framework-defaults",
     installable: true,
 
     srcs: [
@@ -695,12 +695,38 @@
         "libmedia2_jni",
     ],
 
-    javac_shard_size: 150,
-
     dxflags: [
         "--core-library",
         "--multi-dex",
     ],
+
+}
+
+java_library {
+    name: "framework",
+    defaults: ["framework-defaults"],
+    javac_shard_size: 150,
+}
+
+java_library {
+    name: "framework-annotation-proc",
+    defaults: ["framework-defaults"],
+    // Use UsedByApps annotation processor
+    annotation_processors: ["unsupportedappusage-annotation-processor"],
+    // b/25860419: annotation processors must be explicitly specified for grok
+    annotation_processor_classes: [
+        "android.processor.unsupportedappusage.UsedByAppsProcessor",
+    ],
+}
+
+// A host library including just UnsupportedAppUsage.java so that the annotation
+// processor can also use this annotation.
+java_library_host {
+    name: "unsupportedappusage-annotation",
+    srcs: [
+        "core/java/android/annotation/IntDef.java",
+        "core/java/android/annotation/UnsupportedAppUsage.java",
+    ],
 }
 
 // A temporary build target that is conditionally included on the bootclasspath if
diff --git a/tools/processors/unsupportedappusage/Android.bp b/tools/processors/unsupportedappusage/Android.bp
new file mode 100644
index 0000000..98f3c95
--- /dev/null
+++ b/tools/processors/unsupportedappusage/Android.bp
@@ -0,0 +1,15 @@
+
+java_library_host {
+    name: "unsupportedappusage-annotation-processor",
+    java_resources: [
+        "META-INF/**/*",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "guava",
+        "unsupportedappusage-annotation"
+    ],
+    use_tools_jar: true,
+}
diff --git a/tools/processors/unsupportedappusage/META-INF/services/javax.annotation.processing.Processor b/tools/processors/unsupportedappusage/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..4a969d3
--- /dev/null
+++ b/tools/processors/unsupportedappusage/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.processor.unsupportedappusage.UnsupportedAppUsageProcessor
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
new file mode 100644
index 0000000..ef29146
--- /dev/null
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.unsupportedappusage;
+
+import static javax.lang.model.element.ElementKind.PACKAGE;
+import static javax.tools.Diagnostic.Kind.ERROR;
+import static javax.tools.Diagnostic.Kind.WARNING;
+
+import android.annotation.UnsupportedAppUsage;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.sun.tools.javac.code.Type;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.processing.Messager;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Builds a dex signature for a given method or field.
+ */
+public class SignatureBuilder {
+
+    private static final Map<TypeKind, String> TYPE_MAP = ImmutableMap.<TypeKind, String>builder()
+            .put(TypeKind.BOOLEAN, "Z")
+            .put(TypeKind.BYTE, "B")
+            .put(TypeKind.CHAR, "C")
+            .put(TypeKind.DOUBLE, "D")
+            .put(TypeKind.FLOAT, "F")
+            .put(TypeKind.INT, "I")
+            .put(TypeKind.LONG, "J")
+            .put(TypeKind.SHORT, "S")
+            .put(TypeKind.VOID, "V")
+            .build();
+
+    private final Messager mMessager;
+
+    /**
+     * Exception used internally when we can't build a signature. Whenever this is thrown, an error
+     * will also be written to the Messager.
+     */
+    private class SignatureBuilderException extends Exception {
+        public SignatureBuilderException(String message) {
+            super(message);
+        }
+        public void report(Element offendingElement) {
+            mMessager.printMessage(ERROR, getMessage(), offendingElement);
+        }
+    }
+
+    public SignatureBuilder(Messager messager) {
+        mMessager = messager;
+    }
+
+    /**
+     * Returns a list of enclosing elements for the given element, with the package first, and
+     * excluding the element itself.
+     */
+    private List<Element> getEnclosingElements(Element e) {
+        List<Element> enclosing = new ArrayList<>();
+        e = e.getEnclosingElement(); // don't include the element itself.
+        while (e != null) {
+            enclosing.add(e);
+            e = e.getEnclosingElement();
+        }
+        Collections.reverse(enclosing);
+        return enclosing;
+    }
+
+    /**
+     * Get the dex signature for a clazz, in format "Lpackage/name/Outer$Inner;"
+     */
+    private String getClassSignature(TypeElement clazz) {
+        StringBuilder sb = new StringBuilder("L");
+        for (Element enclosing : getEnclosingElements(clazz)) {
+            if (enclosing.getKind() == PACKAGE) {
+                sb.append(((PackageElement) enclosing)
+                        .getQualifiedName()
+                        .toString()
+                        .replace('.', '/'));
+                sb.append('/');
+            } else {
+                sb.append(enclosing.getSimpleName()).append('$');
+            }
+
+        }
+        return sb
+                .append(clazz.getSimpleName())
+                .append(";")
+                .toString();
+    }
+
+    /**
+     * Returns the type signature for a given type. For primitive types, a single character.
+     * For classes, the class signature. For arrays, a "[" preceeding the component type.
+     */
+    private String getTypeSignature(TypeMirror type) throws SignatureBuilderException {
+        String sig = TYPE_MAP.get(type.getKind());
+        if (sig != null) {
+            return sig;
+        }
+        switch (type.getKind()) {
+            case ARRAY:
+                return "[" + getTypeSignature(((ArrayType) type).getComponentType());
+            case DECLARED:
+                Element declaring = ((DeclaredType) type).asElement();
+                if (!(declaring instanceof TypeElement)) {
+                    throw new SignatureBuilderException(
+                            "Can't handle declared type of kind " + declaring.getKind());
+                }
+                return getClassSignature((TypeElement) declaring);
+            case TYPEVAR:
+                Type.TypeVar typeVar = (Type.TypeVar) type;
+                if (typeVar.getLowerBound().getKind() != TypeKind.NULL) {
+                    return getTypeSignature(typeVar.getLowerBound());
+                } else if (typeVar.getUpperBound().getKind() != TypeKind.NULL) {
+                    return getTypeSignature(typeVar.getUpperBound());
+                } else {
+                    throw new SignatureBuilderException("Can't handle typevar with no bound");
+                }
+
+            default:
+                throw new SignatureBuilderException("Can't handle type of kind " + type.getKind());
+        }
+    }
+
+    /**
+     * Get the signature for an executable, either a method or a constructor.
+     *
+     * @param name "<init>" for  constructor, else the method name
+     * @param method The executable element in question.
+     */
+    private String getExecutableSignature(CharSequence name, ExecutableElement method)
+            throws SignatureBuilderException {
+        StringBuilder sig = new StringBuilder();
+        sig.append(getClassSignature((TypeElement) method.getEnclosingElement()))
+                .append("->")
+                .append(name)
+                .append("(");
+        for (VariableElement param : method.getParameters()) {
+            sig.append(getTypeSignature(param.asType()));
+        }
+        sig.append(")")
+                .append(getTypeSignature(method.getReturnType()));
+        return sig.toString();
+    }
+
+    private String buildMethodSignature(ExecutableElement method) throws SignatureBuilderException {
+        return getExecutableSignature(method.getSimpleName(), method);
+    }
+
+    private String buildConstructorSignature(ExecutableElement cons)
+            throws SignatureBuilderException {
+        return getExecutableSignature("<init>", cons);
+    }
+
+    private String buildFieldSignature(VariableElement field) throws SignatureBuilderException {
+        StringBuilder sig = new StringBuilder();
+        sig.append(getClassSignature((TypeElement) field.getEnclosingElement()))
+                .append("->")
+                .append(field.getSimpleName())
+                .append(":")
+                .append(getTypeSignature(field.asType()))
+        ;
+        return sig.toString();
+    }
+
+    public String buildSignature(Element element) {
+        UnsupportedAppUsage uba = element.getAnnotation(UnsupportedAppUsage.class);
+        try {
+            String signature;
+            switch (element.getKind()) {
+                case METHOD:
+                    signature = buildMethodSignature((ExecutableElement) element);
+                    break;
+                case CONSTRUCTOR:
+                    signature = buildConstructorSignature((ExecutableElement) element);
+                    break;
+                case FIELD:
+                    signature = buildFieldSignature((VariableElement) element);
+                    break;
+                default:
+                    return null;
+            }
+            // if we have an expected signature on the annotation, warn if it doesn't match.
+            if (!Strings.isNullOrEmpty(uba.expectedSignature())) {
+                if (!signature.equals(uba.expectedSignature())) {
+                    mMessager.printMessage(
+                            WARNING,
+                            String.format("Expected signature doesn't match generated signature.\n"
+                                            + " Expected:  %s\n Generated: %s",
+                                    uba.expectedSignature(), signature),
+                            element);
+                }
+            }
+            return signature;
+        } catch (SignatureBuilderException problem) {
+            problem.report(element);
+            return null;
+        }
+    }
+}
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
new file mode 100644
index 0000000..1d4c435
--- /dev/null
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.processor.unsupportedappusage;
+
+import static javax.tools.StandardLocation.CLASS_OUTPUT;
+
+import android.annotation.UnsupportedAppUsage;
+
+import com.google.common.base.Joiner;
+import com.sun.tools.javac.model.JavacElements;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.util.Pair;
+import com.sun.tools.javac.util.Position;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * Annotation processor for {@link UnsupportedAppUsage} annotations.
+ *
+ * This processor currently outputs two things:
+ * 1. A greylist.txt containing dex signatures of all annotated elements.
+ * 2. A CSV file with a mapping of dex signatures to corresponding source positions.
+ *
+ * The first will be used at a later stage of the build to add access flags to the dex file. The
+ * second is used for automating updates to the annotations themselves.
+ */
+@SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage"})
+public class UnsupportedAppUsageProcessor extends AbstractProcessor {
+
+    // Package name for writing output. Output will be written to the "class output" location within
+    // this package.
+    private static final String PACKAGE = "unsupportedappusage";
+    private static final String INDEX_CSV = "unsupportedappusage_index.csv";
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latest();
+    }
+
+    /**
+     * Write the contents of a stream to a text file, with one line per item.
+     */
+    private void writeToFile(String name,
+            String headerLine,
+            Stream<?> contents) throws IOException {
+        PrintStream out = new PrintStream(processingEnv.getFiler().createResource(
+                CLASS_OUTPUT,
+                PACKAGE,
+                name)
+                .openOutputStream());
+        out.println(headerLine);
+        contents.forEach(o -> out.println(o));
+        if (out.checkError()) {
+            throw new IOException("Error when writing to " + name);
+        }
+        out.close();
+    }
+
+    /**
+     * Find the annotation mirror for the @UnsupportedAppUsage annotation on the given element.
+     */
+    private AnnotationMirror getUnsupportedAppUsageAnnotationMirror(Element e) {
+        for (AnnotationMirror m : e.getAnnotationMirrors()) {
+            TypeElement type = (TypeElement) m.getAnnotationType().asElement();
+            if (type.getQualifiedName().toString().equals(
+                    UnsupportedAppUsage.class.getCanonicalName())) {
+                return m;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a CSV header line for the columns returned by
+     * {@link #getAnnotationIndex(String, Element)}.
+     */
+    private String getCsvHeaders() {
+        return Joiner.on(',').join(
+                "signature",
+                "file",
+                "startline",
+                "startcol",
+                "endline",
+                "endcol"
+        );
+    }
+
+    /**
+     * Maps an annotated element to the source position of the @UnsupportedAppUsage annotation
+     * attached to it. It returns CSV in the format:
+     *   dex-signature,filename,start-line,start-col,end-line,end-col
+     *
+     * The positions refer to the annotation itself, *not* the annotated member. This can therefore
+     * be used to read just the annotation from the file, and to perform in-place edits on it.
+     *
+     * @param signature the dex signature for the element.
+     * @param annotatedElement The annotated element
+     * @return A single line of CSV text
+     */
+    private String getAnnotationIndex(String signature, Element annotatedElement) {
+        JavacElements javacElem = (JavacElements) processingEnv.getElementUtils();
+        AnnotationMirror unsupportedAppUsage =
+                getUnsupportedAppUsageAnnotationMirror(annotatedElement);
+        Pair<JCTree, JCTree.JCCompilationUnit> pair =
+                javacElem.getTreeAndTopLevel(annotatedElement, unsupportedAppUsage, null);
+        Position.LineMap lines = pair.snd.lineMap;
+        return Joiner.on(",").join(
+                signature,
+                pair.snd.getSourceFile().getName(),
+                lines.getLineNumber(pair.fst.pos().getStartPosition()),
+                lines.getColumnNumber(pair.fst.pos().getStartPosition()),
+                lines.getLineNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)),
+                lines.getColumnNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)));
+    }
+
+    /**
+     * This is the main entry point in the processor, called by the compiler.
+     */
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(
+                UnsupportedAppUsage.class);
+        if (annotated.size() == 0) {
+            return true;
+        }
+        // build signatures for each annotated member, and put them in a map of signature to member
+        Map<String, Element> signatureMap = new TreeMap<>();
+        SignatureBuilder sb = new SignatureBuilder(processingEnv.getMessager());
+        for (Element e : annotated) {
+            String sig = sb.buildSignature(e);
+            if (sig != null) {
+                signatureMap.put(sig, e);
+            }
+        }
+        try {
+            writeToFile(INDEX_CSV,
+                    getCsvHeaders(),
+                    signatureMap.entrySet()
+                            .stream()
+                            .map(e -> getAnnotationIndex(e.getKey() ,e.getValue())));
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to write output", e);
+        }
+        return true;
+    }
+}