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