|  | // 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.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"; | 
|  |  | 
|  | 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; | 
|  | } | 
|  | if (arg.equals("none")) { | 
|  | return DiagnosticsLevel.NONE; | 
|  | } | 
|  | errorHandler.accept( | 
|  | new StringDiagnostic( | 
|  | "Invalid diagnostics level '" | 
|  | + arg | 
|  | + "'. Valid levels are 'error', 'warning', 'info' and 'none'.", | 
|  | 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); | 
|  | } | 
|  | } | 
|  | } |