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