blob: 631b5b3f66ef9897751c120841652a1667d3fe81 [file] [log] [blame]
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.Consumer;
public class BaseCompilerCommandParser<
C extends BaseCompilerCommand, B extends BaseCompilerCommand.Builder<C, B>> {
protected static final String MIN_API_FLAG = "--min-api";
protected static final String THREAD_COUNT_FLAG = "--thread-count";
protected static final String MAP_DIAGNOSTICS = "--map-diagnostics";
protected static final String DUMP_INPUT_TO_FILE = "--dumpinputtofile";
protected static final String DUMP_INPUT_TO_DIRECTORY = "--dumpinputtodirectory";
static final Iterable<String> ASSERTIONS_USAGE_MESSAGE =
Arrays.asList(
" --force-enable-assertions[:[<class name>|<package name>...]]",
" --force-ea[:[<class name>|<package name>...]]",
" # Forcefully enable javac generated assertion code.",
" --force-disable-assertions[:[<class name>|<package name>...]]",
" --force-da[:[<class name>|<package name>...]]",
" # Forcefully disable javac generated assertion code. This",
" # is the default handling of javac assertion code when",
" # generating DEX file format.",
" --force-passthrough-assertions[:[<class name>|<package name>...]]",
" --force-pa[:[<class name>|<package name>...]]",
" # Don't change javac generated assertion code. This",
" # is the default handling of javac assertion code when",
" # generating class file format.",
" --force-assertions-handler:<handler method>[:[<class name>|<package name>...]]",
" --force-ah:<handler method>[:[<class name>|<package name>...]]",
" # Change javac and kotlinc generated assertion code to invoke",
" # the method <handler method> with each assertion error",
" # instead of throwing it. The <handler method> is specified"
+ " as",
" # a class name followed by a dot and the method name. The",
" # handler method must take a single argument of type",
" # java.lang.Throwable and have return type void.");
static final Iterable<String> THREAD_COUNT_USAGE_MESSAGE =
Arrays.asList(
" " + THREAD_COUNT_FLAG + " <number of threads>",
" # Number of threads to use for compilation. If not specified",
" # the number will be based on heuristics taking the number",
" # of cores into account.");
public static final Iterable<String> MAP_DIAGNOSTICS_USAGE_MESSAGE =
Arrays.asList(
" " + MAP_DIAGNOSTICS + "[:<type>] <from-level> <to-level>",
" # Map diagnostics of <type> (default any) reported as",
" # <from-level> to <to-level> where <from-level> and",
" # <to-level> are one of 'info', 'warning', or 'error' and the",
" # optional <type> is either the simple or fully qualified",
" # Java type name of a diagnostic. If <type> is unspecified,",
" # all diagnostics at <from-level> will be mapped.",
" # Note that fatal compiler errors cannot be mapped.");
public static void parsePositiveIntArgument(
Consumer<Diagnostic> errorConsumer,
String flag,
String argument,
Origin origin,
Consumer<Integer> setter) {
int value;
try {
value = Integer.parseInt(argument);
} catch (NumberFormatException e) {
errorConsumer.accept(
new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
return;
}
if (value < 1) {
errorConsumer.accept(
new StringDiagnostic("Invalid argument to " + flag + ": " + argument, origin));
return;
}
setter.accept(value);
}
private static String PACKAGE_ASSERTION_POSTFIX = "...";
private enum AssertionTransformationType {
ENABLE,
DISABLE,
PASSTHROUGH,
HANDLER
}
private AssertionsConfiguration.Builder prepareBuilderForScope(
AssertionsConfiguration.Builder builder,
AssertionTransformationType transformation,
MethodReference assertionHandler) {
switch (transformation) {
case ENABLE:
return builder.setCompileTimeEnable();
case DISABLE:
return builder.setCompileTimeDisable();
case PASSTHROUGH:
return builder.setPassthrough();
case HANDLER:
return builder.setAssertionHandler(assertionHandler);
default:
throw new Unreachable();
}
}
private void addAssertionTransformation(
B builder,
AssertionTransformationType transformation,
MethodReference assertionHandler,
String scope) {
if (scope == null) {
builder.addAssertionsConfiguration(
b -> prepareBuilderForScope(b, transformation, assertionHandler).setScopeAll().build());
} else {
assert scope.length() > 0;
if (scope.endsWith(PACKAGE_ASSERTION_POSTFIX)) {
builder.addAssertionsConfiguration(
b ->
prepareBuilderForScope(b, transformation, assertionHandler)
.setScopePackage(
scope.substring(0, scope.length() - PACKAGE_ASSERTION_POSTFIX.length()))
.build());
} else {
builder.addAssertionsConfiguration(
b ->
prepareBuilderForScope(b, transformation, assertionHandler)
.setScopeClass(scope)
.build());
}
}
}
boolean tryParseAssertionArgument(B builder, String arg, Origin origin) {
String FORCE_ENABLE_ASSERTIONS = "--force-enable-assertions";
String FORCE_EA = "--force-ea";
String FORCE_DISABLE_ASSERTIONS = "--force-disable-assertions";
String FORCE_DA = "--force-da";
String FORCE_PASSTHROUGH_ASSERTIONS = "--force-passthrough-assertions";
String FORCE_PA = "--force-pa";
String FORCE_ASSERTIONS_HANDLER = "--force-assertions-handler";
String FORCE_AH = "--force-ah";
AssertionTransformationType transformation = null;
MethodReference assertionsHandler = null;
String remaining = null;
if (arg.startsWith(FORCE_ENABLE_ASSERTIONS)) {
transformation = AssertionTransformationType.ENABLE;
remaining = arg.substring(FORCE_ENABLE_ASSERTIONS.length());
} else if (arg.startsWith(FORCE_EA)) {
transformation = AssertionTransformationType.ENABLE;
remaining = arg.substring(FORCE_EA.length());
} else if (arg.startsWith(FORCE_DISABLE_ASSERTIONS)) {
transformation = AssertionTransformationType.DISABLE;
remaining = arg.substring(FORCE_DISABLE_ASSERTIONS.length());
} else if (arg.startsWith(FORCE_DA)) {
transformation = AssertionTransformationType.DISABLE;
remaining = arg.substring(FORCE_DA.length());
} else if (arg.startsWith(FORCE_PASSTHROUGH_ASSERTIONS)) {
transformation = AssertionTransformationType.PASSTHROUGH;
remaining = arg.substring(FORCE_PASSTHROUGH_ASSERTIONS.length());
} else if (arg.startsWith(FORCE_PA)) {
transformation = AssertionTransformationType.PASSTHROUGH;
remaining = arg.substring(FORCE_PA.length());
} else if (arg.startsWith(FORCE_ASSERTIONS_HANDLER)) {
transformation = AssertionTransformationType.HANDLER;
remaining = arg.substring(FORCE_ASSERTIONS_HANDLER.length());
} else if (arg.startsWith(FORCE_AH)) {
transformation = AssertionTransformationType.HANDLER;
remaining = arg.substring(FORCE_AH.length());
}
if (transformation == AssertionTransformationType.HANDLER) {
if (remaining.length() == 0 || (remaining.length() == 1 && remaining.charAt(0) == ':')) {
throw builder.fatalError(
new StringDiagnostic("Missing required argument <handler method>", origin));
}
if (remaining.charAt(0) != ':') {
return false;
}
remaining = remaining.substring(1);
int index = remaining.indexOf(':');
if (index == 0) {
throw builder.fatalError(
new StringDiagnostic("Missing required argument <handler method>", origin));
}
String assertionsHandlerString = index > 0 ? remaining.substring(0, index) : remaining;
int lastDotIndex = assertionsHandlerString.lastIndexOf('.');
if (assertionsHandlerString.length() < 3
|| lastDotIndex <= 0
|| lastDotIndex == assertionsHandlerString.length() - 1
|| !DescriptorUtils.isValidJavaType(assertionsHandlerString.substring(0, lastDotIndex))) {
throw builder.fatalError(
new StringDiagnostic(
"Invalid argument <handler method>: " + assertionsHandlerString, origin));
}
assertionsHandler =
Reference.methodFromDescriptor(
DescriptorUtils.javaTypeToDescriptor(
assertionsHandlerString.substring(0, lastDotIndex)),
assertionsHandlerString.substring(lastDotIndex + 1),
"(Ljava/lang/Throwable;)V");
remaining = remaining.substring(assertionsHandlerString.length());
}
if (transformation != null) {
if (remaining.length() == 0) {
addAssertionTransformation(builder, transformation, assertionsHandler, null);
return true;
} else {
if (remaining.length() == 1 && remaining.charAt(0) == ':') {
throw builder.fatalError(new StringDiagnostic("Missing optional argument", origin));
}
if (remaining.charAt(0) != ':') {
return false;
}
String classOrPackageScope = remaining.substring(1);
if (classOrPackageScope.contains(";")
|| classOrPackageScope.contains("[")
|| classOrPackageScope.contains("/")) {
builder.error(
new StringDiagnostic("Illegal assertion scope: " + classOrPackageScope, origin));
}
addAssertionTransformation(
builder, transformation, assertionsHandler, remaining.substring(1));
return true;
}
} else {
return false;
}
}
int tryParseMapDiagnostics(B builder, String arg, String[] args, int argsIndex, Origin origin) {
return tryParseMapDiagnostics(
builder::error, builder.getReporter(), arg, args, argsIndex, origin);
}
private static DiagnosticsLevel tryParseLevel(
Consumer<Diagnostic> errorHandler, String arg, Origin origin) {
if (arg.equals("error")) {
return DiagnosticsLevel.ERROR;
}
if (arg.equals("warning")) {
return DiagnosticsLevel.WARNING;
}
if (arg.equals("info")) {
return DiagnosticsLevel.INFO;
}
errorHandler.accept(
new StringDiagnostic(
"Invalid diagnostics level '"
+ arg
+ "'. Valid levels are 'error', 'warning' and 'info'.",
origin));
return null;
}
public static int tryParseMapDiagnostics(
Consumer<Diagnostic> errorHandler,
Reporter reporter,
String arg,
String[] args,
int argsIndex,
Origin origin) {
if (!arg.startsWith(MAP_DIAGNOSTICS)) {
return -1;
}
if (args.length <= argsIndex + 2) {
errorHandler.accept(new StringDiagnostic("Missing argument(s) for " + arg + ".", origin));
return args.length - argsIndex;
}
String remaining = arg.substring(MAP_DIAGNOSTICS.length());
String diagnosticsClassName = "";
if (remaining.length() > 0) {
if (remaining.length() == 1 || remaining.charAt(0) != ':') {
errorHandler.accept(
new StringDiagnostic("Invalid diagnostics type specification " + arg + ".", origin));
return 0;
}
diagnosticsClassName = remaining.substring(1);
}
DiagnosticsLevel from = tryParseLevel(errorHandler, args[argsIndex + 1], origin);
DiagnosticsLevel to = tryParseLevel(errorHandler, args[argsIndex + 2], origin);
if (from != null && to != null) {
reporter.addDiagnosticsLevelMapping(from, diagnosticsClassName, to);
}
return 2;
}
int tryParseDump(B builder, String arg, String[] args, int argsIndex, Origin origin) {
if (!arg.equals(DUMP_INPUT_TO_FILE) && !arg.equals(DUMP_INPUT_TO_DIRECTORY)) {
return -1;
}
if (args.length <= argsIndex + 1) {
builder.error(new StringDiagnostic("Missing argument(s) for " + arg + ".", origin));
return args.length - argsIndex;
}
if (arg.equals(DUMP_INPUT_TO_FILE)) {
builder.dumpInputToFile(Paths.get(args[argsIndex + 1]));
} else {
assert arg.equals(DUMP_INPUT_TO_DIRECTORY);
builder.dumpInputToDirectory(Paths.get(args[argsIndex + 1]));
}
return 1;
}
/**
* This method must match the lookup in
* {@link com.android.tools.r8.JdkClassFileProvider#fromJdkHome}.
*/
private static boolean isJdkHome(Path home) {
Path jrtFsJar = home.resolve("lib").resolve("jrt-fs.jar");
if (Files.exists(jrtFsJar)) {
return true;
}
// JDK has rt.jar in jre/lib/rt.jar.
Path rtJar = home.resolve("jre").resolve("lib").resolve("rt.jar");
if (Files.exists(rtJar)) {
return true;
}
// JRE has rt.jar in lib/rt.jar.
rtJar = home.resolve("lib").resolve("rt.jar");
if (Files.exists(rtJar)) {
return true;
}
return false;
}
static void addLibraryArgument(BaseCommand.Builder builder, Origin origin, String arg) {
Path path = Paths.get(arg);
if (isJdkHome(path)) {
try {
builder
.addLibraryResourceProvider(JdkClassFileProvider.fromJdkHome(path));
} catch (IOException e) {
builder.error(new ExceptionDiagnostic(e, origin));
}
} else {
builder.addLibraryFiles(path);
}
}
}