• 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(10000, FLAG_UNSUPPORTED); // VMRuntime.SDK_VERSION_CUR_DEVELOPMENT
80         TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map);
81     }
82 
83     private final Status mStatus;
84     private final String[] mJarFiles;
85     private final AnnotationConsumer mOutput;
86     private final Set<String> mPublicApis;
87 
main(String[] args)88     public static void main(String[] args) {
89         Options options = new Options();
90         options.addOption(OptionBuilder
91                 .withLongOpt("stub-api-flags")
92                 .hasArgs(1)
93                 .withDescription("CSV file with API flags generated from public API stubs. " +
94                         "Used to de-dupe bridge methods.")
95                 .create("s"));
96         options.addOption(OptionBuilder
97                 .withLongOpt("write-flags-csv")
98                 .hasArgs(1)
99                 .withDescription("Specify file to write hiddenapi flags to.")
100                 .create('w'));
101         options.addOption(OptionBuilder
102                 .withLongOpt("debug")
103                 .hasArgs(0)
104                 .withDescription("Enable debug")
105                 .create("d"));
106         options.addOption(OptionBuilder
107                 .withLongOpt("dump-all-members")
108                 .withDescription("Dump all members from jar files to stdout. Ignore annotations. " +
109                         "Do not use in conjunction with any other arguments.")
110                 .hasArgs(0)
111                 .create('m'));
112         options.addOption(OptionBuilder
113                 .withLongOpt("write-metadata-csv")
114                 .hasArgs(1)
115                 .withDescription("Specify a file to write API metaadata to. This is a CSV file " +
116                         "containing any annotation properties for all members. Do not use in " +
117                         "conjunction with --write-flags-csv.")
118                 .create('c'));
119         options.addOption(OptionBuilder
120                 .withLongOpt("help")
121                 .hasArgs(0)
122                 .withDescription("Show this help")
123                 .create('h'));
124 
125         CommandLineParser parser = new GnuParser();
126         CommandLine cmd;
127 
128         try {
129             cmd = parser.parse(options, args);
130         } catch (ParseException e) {
131             System.err.println(e.getMessage());
132             help(options);
133             return;
134         }
135         if (cmd.hasOption('h')) {
136             help(options);
137         }
138 
139 
140         String[] jarFiles = cmd.getArgs();
141         if (jarFiles.length == 0) {
142             System.err.println("Error: no jar files specified.");
143             help(options);
144         }
145 
146         Status status = new Status(cmd.hasOption('d'));
147 
148         if (cmd.hasOption('m')) {
149             dumpAllMembers(status, jarFiles);
150         } else {
151             try {
152                 Class2NonSdkList c2nsl = new Class2NonSdkList(
153                         status,
154                         cmd.getOptionValue('s', null),
155                         cmd.getOptionValue('w', null),
156                         cmd.getOptionValue('c', null),
157                         jarFiles);
158                 c2nsl.main();
159             } catch (IOException e) {
160                 status.error(e);
161             }
162         }
163 
164         if (status.ok()) {
165             System.exit(0);
166         } else {
167             System.exit(1);
168         }
169 
170     }
171 
Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile, String csvMetadataFile, String[] jarFiles)172     private Class2NonSdkList(Status status, String stubApiFlagsFile, String csvFlagsFile,
173             String csvMetadataFile, String[] jarFiles)
174             throws IOException {
175         mStatus = status;
176         mJarFiles = jarFiles;
177         if (csvMetadataFile != null) {
178             mOutput = new AnnotationPropertyWriter(csvMetadataFile);
179         } else {
180             mOutput = new HiddenapiFlagsWriter(csvFlagsFile);
181         }
182 
183         if (stubApiFlagsFile != null) {
184             mPublicApis =
185                     Files.readLines(new File(stubApiFlagsFile), StandardCharsets.UTF_8).stream()
186                         .map(s -> Splitter.on(",").splitToList(s))
187                         .filter(s -> s.contains(FLAG_PUBLIC_API))
188                         .map(s -> s.get(0))
189                         .collect(Collectors.toSet());
190         } else {
191             mPublicApis = Collections.emptySet();
192         }
193     }
194 
createAnnotationHandlers()195     private Map<String, AnnotationHandler> createAnnotationHandlers() {
196         Builder<String, AnnotationHandler> builder = ImmutableMap.builder();
197         UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler =
198                 new UnsupportedAppUsageAnnotationHandler(
199                     mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP);
200 
201         addRepeatedAnnotationHandlers(
202                 builder,
203                 classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION),
204                 classNameToSignature(UNSUPPORTED_APP_USAGE_ANNOTATION + "$Container"),
205                 greylistAnnotationHandler);
206 
207         CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler(
208             mOutput, mPublicApis, FLAG_PUBLIC_API);
209 
210         return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME,
211             CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler)
212             .build();
213     }
214 
classNameToSignature(String a)215     private String classNameToSignature(String a) {
216         return "L" + a.replace('.', '/') + ";";
217     }
218 
219     /**
220      * Add a handler for an annotation as well as an handler for the container annotation that is
221      * used when the annotation is repeated.
222      *
223      * @param builder the builder for the map to which the handlers will be added.
224      * @param annotationName the name of the annotation.
225      * @param containerAnnotationName the name of the annotation container.
226      * @param handler the handler for the annotation.
227      */
addRepeatedAnnotationHandlers( Builder<String, AnnotationHandler> builder, String annotationName, String containerAnnotationName, AnnotationHandler handler)228     private static Builder<String, AnnotationHandler> addRepeatedAnnotationHandlers(
229         Builder<String, AnnotationHandler> builder,
230         String annotationName, String containerAnnotationName,
231         AnnotationHandler handler) {
232         return builder
233             .put(annotationName, handler)
234             .put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler));
235     }
236 
main()237     private void main() {
238         Map<String, AnnotationHandler> handlers = createAnnotationHandlers();
239         for (String jarFile : mJarFiles) {
240             mStatus.debug("Processing jar file %s", jarFile);
241             try {
242                 JarReader reader = new JarReader(mStatus, jarFile);
243                 reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers)
244                         .visit());
245                 reader.close();
246             } catch (IOException e) {
247                 mStatus.error(e);
248             }
249         }
250         mOutput.close();
251     }
252 
dumpAllMembers(Status status, String[] jarFiles)253     private static void dumpAllMembers(Status status, String[] jarFiles) {
254         for (String jarFile : jarFiles) {
255             status.debug("Processing jar file %s", jarFile);
256             try {
257                 JarReader reader = new JarReader(status, jarFile);
258                 reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status)
259                         .visit());
260                 reader.close();
261             } catch (IOException e) {
262                 status.error(e);
263             }
264         }
265     }
266 
help(Options options)267     private static void help(Options options) {
268         new HelpFormatter().printHelp(
269                 "class2nonsdklist path/to/classes.jar [classes2.jar ...]",
270                 "Extracts nonsdk entries from classes jar files given",
271                 options, null, true);
272         System.exit(1);
273     }
274 }
275