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