Home | History | Annotate | Download | only in class2greylist
      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 com.android.class2greylist;
     18 
     19 import com.google.common.annotations.VisibleForTesting;
     20 import com.google.common.base.Splitter;
     21 import com.google.common.collect.ImmutableMap;
     22 import com.google.common.collect.ImmutableMap.Builder;
     23 import com.google.common.collect.ImmutableSet;
     24 import com.google.common.collect.Sets;
     25 import com.google.common.io.Files;
     26 
     27 import org.apache.commons.cli.CommandLine;
     28 import org.apache.commons.cli.CommandLineParser;
     29 import org.apache.commons.cli.GnuParser;
     30 import org.apache.commons.cli.HelpFormatter;
     31 import org.apache.commons.cli.OptionBuilder;
     32 import org.apache.commons.cli.Options;
     33 import org.apache.commons.cli.ParseException;
     34 
     35 import java.io.File;
     36 import java.io.IOException;
     37 import java.nio.charset.Charset;
     38 import java.util.Arrays;
     39 import java.util.Collections;
     40 import java.util.HashMap;
     41 import java.util.Map;
     42 import java.util.Set;
     43 import java.util.stream.Collectors;
     44 
     45 /**
     46  * Build time tool for extracting a list of members from jar files that have the @UsedByApps
     47  * annotation, for building the greylist.
     48  */
     49 public class Class2Greylist {
     50 
     51     private static final Set<String> GREYLIST_ANNOTATIONS =
     52             ImmutableSet.of(
     53                     "android.annotation.UnsupportedAppUsage",
     54                     "dalvik.annotation.compat.UnsupportedAppUsage");
     55     private static final Set<String> WHITELIST_ANNOTATIONS = ImmutableSet.of();
     56 
     57     public static final String FLAG_WHITELIST = "whitelist";
     58     public static final String FLAG_GREYLIST = "greylist";
     59     public static final String FLAG_BLACKLIST = "blacklist";
     60     public static final String FLAG_GREYLIST_MAX_O = "greylist-max-o";
     61     public static final String FLAG_GREYLIST_MAX_P = "greylist-max-p";
     62 
     63     public static final String FLAG_PUBLIC_API = "public-api";
     64 
     65     private static final Map<Integer, String> TARGET_SDK_TO_LIST_MAP;
     66     static {
     67         Map<Integer, String> map = new HashMap<>();
     68         map.put(null, FLAG_GREYLIST);
     69         map.put(26, FLAG_GREYLIST_MAX_O);
     70         map.put(28, FLAG_GREYLIST_MAX_P);
     71         TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map);
     72     }
     73 
     74     private final Status mStatus;
     75     private final String mCsvFlagsFile;
     76     private final String mCsvMetadataFile;
     77     private final String[] mJarFiles;
     78     private final AnnotationConsumer mOutput;
     79     private final Set<String> mPublicApis;
     80 
     81     public static void main(String[] args) {
     82         Options options = new Options();
     83         options.addOption(OptionBuilder
     84                 .withLongOpt("stub-api-flags")
     85                 .hasArgs(1)
     86                 .withDescription("CSV file with API flags generated from public API stubs. " +
     87                         "Used to de-dupe bridge methods.")
     88                 .create("s"));
     89         options.addOption(OptionBuilder
     90                 .withLongOpt("write-flags-csv")
     91                 .hasArgs(1)
     92                 .withDescription("Specify file to write hiddenapi flags to.")
     93                 .create('w'));
     94         options.addOption(OptionBuilder
     95                 .withLongOpt("debug")
     96                 .hasArgs(0)
     97                 .withDescription("Enable debug")
     98                 .create("d"));
     99         options.addOption(OptionBuilder
    100                 .withLongOpt("dump-all-members")
    101                 .withDescription("Dump all members from jar files to stdout. Ignore annotations. " +
    102                         "Do not use in conjunction with any other arguments.")
    103                 .hasArgs(0)
    104                 .create('m'));
    105         options.addOption(OptionBuilder
    106                 .withLongOpt("write-metadata-csv")
    107                 .hasArgs(1)
    108                 .withDescription("Specify a file to write API metaadata to. This is a CSV file " +
    109                         "containing any annotation properties for all members. Do not use in " +
    110                         "conjunction with --write-flags-csv.")
    111                 .create('c'));
    112         options.addOption(OptionBuilder
    113                 .withLongOpt("help")
    114                 .hasArgs(0)
    115                 .withDescription("Show this help")
    116                 .create('h'));
    117 
    118         CommandLineParser parser = new GnuParser();
    119         CommandLine cmd;
    120 
    121         try {
    122             cmd = parser.parse(options, args);
    123         } catch (ParseException e) {
    124             System.err.println(e.getMessage());
    125             help(options);
    126             return;
    127         }
    128         if (cmd.hasOption('h')) {
    129             help(options);
    130         }
    131 
    132 
    133         String[] jarFiles = cmd.getArgs();
    134         if (jarFiles.length == 0) {
    135             System.err.println("Error: no jar files specified.");
    136             help(options);
    137         }
    138 
    139         Status status = new Status(cmd.hasOption('d'));
    140 
    141         if (cmd.hasOption('m')) {
    142             dumpAllMembers(status, jarFiles);
    143         } else {
    144             try {
    145                 Class2Greylist c2gl = new Class2Greylist(
    146                         status,
    147                         cmd.getOptionValue('s', null),
    148                         cmd.getOptionValue('w', null),
    149                         cmd.getOptionValue('c', null),
    150                         jarFiles);
    151                 c2gl.main();
    152             } catch (IOException e) {
    153                 status.error(e);
    154             }
    155         }
    156 
    157         if (status.ok()) {
    158             System.exit(0);
    159         } else {
    160             System.exit(1);
    161         }
    162 
    163     }
    164 
    165     @VisibleForTesting
    166     Class2Greylist(Status status, String stubApiFlagsFile, String csvFlagsFile,
    167             String csvMetadataFile, String[] jarFiles)
    168             throws IOException {
    169         mStatus = status;
    170         mCsvFlagsFile = csvFlagsFile;
    171         mCsvMetadataFile = csvMetadataFile;
    172         mJarFiles = jarFiles;
    173         if (mCsvMetadataFile != null) {
    174             mOutput = new AnnotationPropertyWriter(mCsvMetadataFile);
    175         } else {
    176             mOutput = new HiddenapiFlagsWriter(mCsvFlagsFile);
    177         }
    178 
    179         if (stubApiFlagsFile != null) {
    180             mPublicApis =
    181                     Files.readLines(new File(stubApiFlagsFile), Charset.forName("UTF-8")).stream()
    182                         .map(s -> Splitter.on(",").splitToList(s))
    183                         .filter(s -> s.contains(FLAG_PUBLIC_API))
    184                         .map(s -> s.get(0))
    185                         .collect(Collectors.toSet());
    186         } else {
    187             mPublicApis = Collections.emptySet();
    188         }
    189     }
    190 
    191     private Map<String, AnnotationHandler> createAnnotationHandlers() {
    192         Builder<String, AnnotationHandler> builder = ImmutableMap.builder();
    193         UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler =
    194                 new UnsupportedAppUsageAnnotationHandler(
    195                     mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP);
    196         GREYLIST_ANNOTATIONS
    197             .forEach(a -> addRepeatedAnnotationHandlers(
    198                 builder,
    199                 classNameToSignature(a),
    200                 classNameToSignature(a + "$Container"),
    201                 greylistAnnotationHandler));
    202 
    203         CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler(
    204             mOutput, mPublicApis, FLAG_PUBLIC_API);
    205 
    206         return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME,
    207             CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler)
    208             .build();
    209     }
    210 
    211     private String classNameToSignature(String a) {
    212         return "L" + a.replace('.', '/') + ";";
    213     }
    214 
    215     /**
    216      * Add a handler for an annotation as well as an handler for the container annotation that is
    217      * used when the annotation is repeated.
    218      *
    219      * @param builder the builder for the map to which the handlers will be added.
    220      * @param annotationName the name of the annotation.
    221      * @param containerAnnotationName the name of the annotation container.
    222      * @param handler the handler for the annotation.
    223      */
    224     private static Builder<String, AnnotationHandler> addRepeatedAnnotationHandlers(
    225         Builder<String, AnnotationHandler> builder,
    226         String annotationName, String containerAnnotationName,
    227         AnnotationHandler handler) {
    228         return builder
    229             .put(annotationName, handler)
    230             .put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler));
    231     }
    232 
    233     private void main() throws IOException {
    234         Map<String, AnnotationHandler> handlers = createAnnotationHandlers();
    235         for (String jarFile : mJarFiles) {
    236             mStatus.debug("Processing jar file %s", jarFile);
    237             try {
    238                 JarReader reader = new JarReader(mStatus, jarFile);
    239                 reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers)
    240                         .visit());
    241                 reader.close();
    242             } catch (IOException e) {
    243                 mStatus.error(e);
    244             }
    245         }
    246         mOutput.close();
    247     }
    248 
    249     private static void dumpAllMembers(Status status, String[] jarFiles) {
    250         for (String jarFile : jarFiles) {
    251             status.debug("Processing jar file %s", jarFile);
    252             try {
    253                 JarReader reader = new JarReader(status, jarFile);
    254                 reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status)
    255                         .visit());
    256                 reader.close();
    257             } catch (IOException e) {
    258                 status.error(e);
    259             }
    260         }
    261     }
    262 
    263     private static void help(Options options) {
    264         new HelpFormatter().printHelp(
    265                 "class2greylist path/to/classes.jar [classes2.jar ...]",
    266                 "Extracts greylist entries from classes jar files given",
    267                 options, null, true);
    268         System.exit(1);
    269     }
    270 }
    271