blob: 1da848b0072f6deda5c9a6cb65fdec4590e86b9c [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.class2greylist;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Build time tool for extracting a list of members from jar files that have the @UsedByApps
* annotation, for building the greylist.
*/
public class Class2Greylist {
private static final Set<String> GREYLIST_ANNOTATIONS =
ImmutableSet.of(
"android.annotation.UnsupportedAppUsage",
"dalvik.annotation.compat.UnsupportedAppUsage");
private static final Set<String> WHITELIST_ANNOTATIONS = ImmutableSet.of();
public static final String FLAG_WHITELIST = "whitelist";
public static final String FLAG_GREYLIST = "greylist";
public static final String FLAG_BLACKLIST = "blacklist";
public static final String FLAG_GREYLIST_MAX_O = "greylist-max-o";
public static final String FLAG_GREYLIST_MAX_P = "greylist-max-p";
private static final Map<Integer, String> TARGET_SDK_TO_LIST_MAP;
static {
Map<Integer, String> map = new HashMap<>();
map.put(null, FLAG_GREYLIST);
map.put(26, FLAG_GREYLIST_MAX_O);
map.put(28, FLAG_GREYLIST_MAX_P);
TARGET_SDK_TO_LIST_MAP = Collections.unmodifiableMap(map);
}
private final Status mStatus;
private final String mPublicApiListFile;
private final String mCsvFlagsFile;
private final String mCsvMetadataFile;
private final String[] mJarFiles;
private final AnnotationConsumer mOutput;
private final Set<String> mPublicApis;
public static void main(String[] args) {
Options options = new Options();
options.addOption(OptionBuilder
.withLongOpt("public-api-list")
.hasArgs(1)
.withDescription("Public API list file. Used to de-dupe bridge methods.")
.create("p"));
options.addOption(OptionBuilder
.withLongOpt("write-flags-csv")
.hasArgs(1)
.withDescription("Specify file to write hiddenapi flags to.")
.create('w'));
options.addOption(OptionBuilder
.withLongOpt("debug")
.hasArgs(0)
.withDescription("Enable debug")
.create("d"));
options.addOption(OptionBuilder
.withLongOpt("dump-all-members")
.withDescription("Dump all members from jar files to stdout. Ignore annotations. " +
"Do not use in conjunction with any other arguments.")
.hasArgs(0)
.create('m'));
options.addOption(OptionBuilder
.withLongOpt("write-metadata-csv")
.hasArgs(1)
.withDescription("Specify a file to write API metaadata to. This is a CSV file " +
"containing any annotation properties for all members. Do not use in " +
"conjunction with --write-flags-csv.")
.create('c'));
options.addOption(OptionBuilder
.withLongOpt("help")
.hasArgs(0)
.withDescription("Show this help")
.create('h'));
CommandLineParser parser = new GnuParser();
CommandLine cmd;
try {
cmd = parser.parse(options, args);
} catch (ParseException e) {
System.err.println(e.getMessage());
help(options);
return;
}
if (cmd.hasOption('h')) {
help(options);
}
String[] jarFiles = cmd.getArgs();
if (jarFiles.length == 0) {
System.err.println("Error: no jar files specified.");
help(options);
}
Status status = new Status(cmd.hasOption('d'));
if (cmd.hasOption('m')) {
dumpAllMembers(status, jarFiles);
} else {
try {
Class2Greylist c2gl = new Class2Greylist(
status,
cmd.getOptionValue('p', null),
cmd.getOptionValue('w', null),
cmd.getOptionValue('c', null),
jarFiles);
c2gl.main();
} catch (IOException e) {
status.error(e);
}
}
if (status.ok()) {
System.exit(0);
} else {
System.exit(1);
}
}
@VisibleForTesting
Class2Greylist(Status status, String publicApiListFile, String csvFlagsFile,
String csvMetadataFile, String[] jarFiles)
throws IOException {
mStatus = status;
mPublicApiListFile = publicApiListFile;
mCsvFlagsFile = csvFlagsFile;
mCsvMetadataFile = csvMetadataFile;
mJarFiles = jarFiles;
if (mCsvMetadataFile != null) {
mOutput = new AnnotationPropertyWriter(mCsvMetadataFile);
} else {
mOutput = new HiddenapiFlagsWriter(mCsvFlagsFile);
}
if (mPublicApiListFile != null) {
mPublicApis = Sets.newHashSet(
Files.readLines(new File(mPublicApiListFile), Charset.forName("UTF-8")));
} else {
mPublicApis = Collections.emptySet();
}
}
private Map<String, AnnotationHandler> createAnnotationHandlers() {
Builder<String, AnnotationHandler> builder = ImmutableMap.builder();
UnsupportedAppUsageAnnotationHandler greylistAnnotationHandler =
new UnsupportedAppUsageAnnotationHandler(
mStatus, mOutput, mPublicApis, TARGET_SDK_TO_LIST_MAP);
GREYLIST_ANNOTATIONS
.forEach(a -> addRepeatedAnnotationHandlers(
builder,
classNameToSignature(a),
classNameToSignature(a + "$Container"),
greylistAnnotationHandler));
CovariantReturnTypeHandler covariantReturnTypeHandler = new CovariantReturnTypeHandler(
mOutput, mPublicApis, FLAG_WHITELIST);
return addRepeatedAnnotationHandlers(builder, CovariantReturnTypeHandler.ANNOTATION_NAME,
CovariantReturnTypeHandler.REPEATED_ANNOTATION_NAME, covariantReturnTypeHandler)
.build();
}
private String classNameToSignature(String a) {
return "L" + a.replace('.', '/') + ";";
}
/**
* Add a handler for an annotation as well as an handler for the container annotation that is
* used when the annotation is repeated.
*
* @param builder the builder for the map to which the handlers will be added.
* @param annotationName the name of the annotation.
* @param containerAnnotationName the name of the annotation container.
* @param handler the handler for the annotation.
*/
private static Builder<String, AnnotationHandler> addRepeatedAnnotationHandlers(
Builder<String, AnnotationHandler> builder,
String annotationName, String containerAnnotationName,
AnnotationHandler handler) {
return builder
.put(annotationName, handler)
.put(containerAnnotationName, new RepeatedAnnotationHandler(annotationName, handler));
}
private void main() throws IOException {
Map<String, AnnotationHandler> handlers = createAnnotationHandlers();
for (String jarFile : mJarFiles) {
mStatus.debug("Processing jar file %s", jarFile);
try {
JarReader reader = new JarReader(mStatus, jarFile);
reader.stream().forEach(clazz -> new AnnotationVisitor(clazz, mStatus, handlers)
.visit());
reader.close();
} catch (IOException e) {
mStatus.error(e);
}
}
mOutput.close();
}
private static void dumpAllMembers(Status status, String[] jarFiles) {
for (String jarFile : jarFiles) {
status.debug("Processing jar file %s", jarFile);
try {
JarReader reader = new JarReader(status, jarFile);
reader.stream().forEach(clazz -> new MemberDumpingVisitor(clazz, status)
.visit());
reader.close();
} catch (IOException e) {
status.error(e);
}
}
}
private static void help(Options options) {
new HelpFormatter().printHelp(
"class2greylist path/to/classes.jar [classes2.jar ...]",
"Extracts greylist entries from classes jar files given",
options, null, true);
System.exit(1);
}
}