| // Copyright (c) 2016, 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.utils; |
| |
| import static com.android.tools.r8.utils.AndroidApiLevel.B; |
| import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault; |
| |
| import com.android.tools.r8.ClassFileConsumer; |
| import com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.DataResourceConsumer; |
| import com.android.tools.r8.DesugarGraphConsumer; |
| import com.android.tools.r8.DexFilePerClassFileConsumer; |
| import com.android.tools.r8.DexIndexedConsumer; |
| import com.android.tools.r8.FeatureSplit; |
| import com.android.tools.r8.GlobalSyntheticsConsumer; |
| import com.android.tools.r8.MapIdProvider; |
| import com.android.tools.r8.ProgramConsumer; |
| import com.android.tools.r8.SourceFileProvider; |
| import com.android.tools.r8.StringConsumer; |
| import com.android.tools.r8.Version; |
| import com.android.tools.r8.androidapi.ComputedApiLevel; |
| import com.android.tools.r8.cf.CfVersion; |
| import com.android.tools.r8.debuginfo.DebugRepresentation; |
| import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver; |
| import com.android.tools.r8.dex.Marker; |
| import com.android.tools.r8.dex.Marker.Backend; |
| import com.android.tools.r8.dex.Marker.Tool; |
| import com.android.tools.r8.dex.MixedSectionLayoutStrategy; |
| import com.android.tools.r8.dex.VirtualFile; |
| import com.android.tools.r8.dump.DumpOptions; |
| import com.android.tools.r8.errors.CompilationError; |
| import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic; |
| import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic; |
| import com.android.tools.r8.errors.InvalidDebugInfoException; |
| import com.android.tools.r8.errors.InvalidLibrarySuperclassDiagnostic; |
| import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic; |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.experimental.graphinfo.GraphConsumer; |
| import com.android.tools.r8.experimental.startup.StartupOptions; |
| import com.android.tools.r8.experimental.startup.instrumentation.StartupInstrumentationOptions; |
| import com.android.tools.r8.features.FeatureSplitConfiguration; |
| import com.android.tools.r8.graph.AppInfoWithClassHierarchy; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexClasspathClass; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItem; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexLibraryClass; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexReference; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses; |
| import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger; |
| import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses; |
| import com.android.tools.r8.horizontalclassmerging.Policy; |
| import com.android.tools.r8.inspector.internal.InspectorImpl; |
| import com.android.tools.r8.ir.analysis.proto.ProtoReferences; |
| import com.android.tools.r8.ir.analysis.type.TypeElement; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.desugar.TypeRewriter; |
| import com.android.tools.r8.ir.desugar.TypeRewriter.MachineDesugarPrefixRewritingMapper; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification; |
| import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; |
| import com.android.tools.r8.ir.desugar.nest.Nest; |
| import com.android.tools.r8.ir.optimize.Inliner; |
| import com.android.tools.r8.ir.optimize.enums.EnumDataMap; |
| import com.android.tools.r8.naming.ClassNameMapper; |
| import com.android.tools.r8.naming.MapVersion; |
| import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.position.Position; |
| import com.android.tools.r8.profile.art.ArtProfileOptions; |
| import com.android.tools.r8.references.ClassReference; |
| import com.android.tools.r8.references.FieldReference; |
| import com.android.tools.r8.references.MethodReference; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration; |
| import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.shaking.Enqueuer; |
| import com.android.tools.r8.shaking.GlobalKeepInfoConfiguration; |
| import com.android.tools.r8.shaking.ProguardConfiguration; |
| import com.android.tools.r8.shaking.ProguardConfigurationRule; |
| import com.android.tools.r8.utils.IROrdering.IdentityIROrdering; |
| import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering; |
| import com.android.tools.r8.utils.collections.ProgramMethodSet; |
| import com.android.tools.r8.utils.structural.Ordered; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Equivalence.Wrapper; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.function.BiConsumer; |
| import java.util.function.BiFunction; |
| import java.util.function.BiPredicate; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import org.objectweb.asm.Opcodes; |
| |
| public class InternalOptions implements GlobalKeepInfoConfiguration { |
| |
| // Set to true to run compilation in a single thread and without randomly shuffling the input. |
| // This makes life easier when running R8 in a debugger. |
| public static final boolean DETERMINISTIC_DEBUGGING = |
| System.getProperty("com.android.tools.r8.deterministicdebugging") != null; |
| |
| // Use a MethodCollection where most interleavings between reading and mutating is caught. |
| public static final boolean USE_METHOD_COLLECTION_CONCURRENCY_CHECKED = false; |
| |
| public enum LineNumberOptimization { |
| OFF, |
| ON |
| } |
| |
| public enum DesugarState { |
| OFF, |
| ON; |
| |
| public boolean isOff() { |
| return this == OFF; |
| } |
| |
| public boolean isOn() { |
| return this == ON; |
| } |
| } |
| |
| public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V19; |
| |
| public static final int SUPPORTED_DEX_VERSION = |
| AndroidApiLevel.LATEST.getDexVersion().getIntValue(); |
| public static final int EXPERIMENTAL_DEX_VERSION = DexVersion.V40.getIntValue(); |
| |
| public static final int ASM_VERSION = Opcodes.ASM9; |
| |
| public final DexItemFactory itemFactory; |
| |
| public DexItemFactory dexItemFactory() { |
| return itemFactory; |
| } |
| |
| public boolean hasProguardConfiguration() { |
| return proguardConfiguration != null; |
| } |
| |
| public ProguardConfiguration getProguardConfiguration() { |
| return proguardConfiguration; |
| } |
| |
| private final ProguardConfiguration proguardConfiguration; |
| public final Reporter reporter; |
| |
| // TODO(zerny): Make this private-final once we have full program-consumer support. |
| public ProgramConsumer programConsumer = null; |
| |
| public ProgramClassConflictResolver programClassConflictResolver = null; |
| |
| private GlobalSyntheticsConsumer globalSyntheticsConsumer = null; |
| |
| public DataResourceConsumer dataResourceConsumer; |
| public FeatureSplitConfiguration featureSplitConfiguration; |
| |
| public List<Consumer<InspectorImpl>> outputInspections = Collections.emptyList(); |
| |
| // Constructor for testing and/or other utilities. |
| public InternalOptions() { |
| reporter = new Reporter(); |
| itemFactory = new DexItemFactory(); |
| proguardConfiguration = null; |
| enableTreeShaking = false; |
| enableMinification = false; |
| } |
| |
| // Constructor for D8, L8, Lint and other non-shrinkers. |
| public InternalOptions(DexItemFactory factory, Reporter reporter) { |
| assert reporter != null; |
| assert factory != null; |
| this.reporter = reporter; |
| itemFactory = factory; |
| proguardConfiguration = null; |
| enableTreeShaking = false; |
| enableMinification = false; |
| disableGlobalOptimizations(); |
| } |
| |
| // Constructor for R8. |
| public InternalOptions( |
| CompilationMode mode, ProguardConfiguration proguardConfiguration, Reporter reporter) { |
| assert reporter != null; |
| assert proguardConfiguration != null; |
| this.debug = mode == CompilationMode.DEBUG; |
| this.reporter = reporter; |
| this.proguardConfiguration = proguardConfiguration; |
| itemFactory = proguardConfiguration.getDexItemFactory(); |
| enableTreeShaking = proguardConfiguration.isShrinking(); |
| enableMinification = proguardConfiguration.isObfuscating(); |
| if (!proguardConfiguration.isOptimizing()) { |
| // TODO(b/171457102): Avoid the need for this. |
| // -dontoptimize disables optimizations by flipping related flags. |
| disableAllOptimizations(); |
| } |
| if (debug) { |
| assert !isMinifying(); |
| assert !isOptimizing(); |
| keepDebugRelatedInformation(); |
| } |
| configurationDebugging = proguardConfiguration.isConfigurationDebugging(); |
| if (proguardConfiguration.isProtoShrinkingEnabled()) { |
| enableProtoShrinking(); |
| } |
| } |
| |
| private void keepDebugRelatedInformation() { |
| assert !proguardConfiguration.isObfuscating(); |
| getProguardConfiguration().getKeepAttributes().sourceFile = true; |
| getProguardConfiguration().getKeepAttributes().sourceDebugExtension = true; |
| getProguardConfiguration().getKeepAttributes().lineNumberTable = true; |
| getProguardConfiguration().getKeepAttributes().localVariableTable = true; |
| getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true; |
| } |
| |
| void enableProtoShrinking() { |
| enableFieldBitAccessAnalysis = true; |
| protoShrinking.enableGeneratedMessageLiteShrinking = true; |
| protoShrinking.enableGeneratedMessageLiteBuilderShrinking = true; |
| protoShrinking.enableGeneratedExtensionRegistryShrinking = true; |
| protoShrinking.enableEnumLiteProtoShrinking = true; |
| } |
| |
| void disableAllOptimizations() { |
| disableGlobalOptimizations(); |
| enableNameReflectionOptimization = false; |
| enableStringConcatenationOptimization = false; |
| } |
| |
| public void disableGlobalOptimizations() { |
| inlinerOptions.enableInlining = false; |
| enableClassInlining = false; |
| enableDevirtualization = false; |
| enableVerticalClassMerging = false; |
| enableEnumUnboxing = false; |
| outline.enabled = false; |
| enableEnumValueOptimization = false; |
| enableSideEffectAnalysis = false; |
| enableTreeShakingOfLibraryMethodOverrides = false; |
| enableInitializedClassesAnalysis = false; |
| callSiteOptimizationOptions.disableOptimization(); |
| horizontalClassMergerOptions.setRestrictToSynthetics(); |
| } |
| |
| public void configureAndroidPlatformBuild(boolean isAndroidPlatformBuild) { |
| assert !androidPlatformBuild; |
| if (isAndroidPlatformBuildOrMinApiPlatform()) { |
| apiModelingOptions().disableApiModeling(); |
| } |
| if (!isAndroidPlatformBuild) { |
| return; |
| } |
| // Configure options according to platform build assumptions. |
| // See go/r8platformflag and b/232073181. |
| androidPlatformBuild = isAndroidPlatformBuild; |
| enableBackportMethods = false; |
| } |
| |
| public boolean isAndroidPlatformBuild() { |
| return androidPlatformBuild; |
| } |
| |
| public boolean isAndroidPlatformBuildOrMinApiPlatform() { |
| return androidPlatformBuild || minApiLevel.isPlatform(); |
| } |
| |
| public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null; |
| // To print memory one also have to enable printtimes. |
| public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null; |
| |
| // Flag to toggle if DEX code objects should pass-through without IR processing. |
| public boolean passthroughDexCode = false; |
| |
| public static class NeverMergeGroup<T> { |
| private final List<T> prefixes; |
| private final List<T> exceptionPrefixes; |
| |
| NeverMergeGroup(List<T> prefixes, List<T> exceptionPrefixes) { |
| this.prefixes = prefixes; |
| this.exceptionPrefixes = exceptionPrefixes; |
| } |
| |
| public List<T> getPrefixes() { |
| return prefixes; |
| } |
| |
| public List<T> getExceptionPrefixes() { |
| return exceptionPrefixes; |
| } |
| |
| public <R> NeverMergeGroup<R> map(Function<T, R> fn) { |
| return new NeverMergeGroup<>( |
| prefixes.stream().map(fn).collect(Collectors.toList()), |
| exceptionPrefixes.stream().map(fn).collect(Collectors.toList())); |
| } |
| } |
| |
| // Flag to toggle if the prefix based merge restriction should be enforced. |
| public boolean enableNeverMergePrefixes = true; |
| public NeverMergeGroup<String> neverMerge = |
| new NeverMergeGroup<>(ImmutableList.of("j$."), ImmutableList.of("java.")); |
| |
| public boolean classpathInterfacesMayHaveStaticInitialization = false; |
| public boolean libraryInterfacesMayHaveStaticInitialization = false; |
| |
| // Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations. |
| public boolean enableFieldBitAccessAnalysis = |
| System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null; |
| public boolean enableVerticalClassMerging = true; |
| public boolean enableUnusedInterfaceRemoval = true; |
| public boolean enableDevirtualization = true; |
| public boolean enableEnumUnboxing = true; |
| public boolean enableSimpleInliningConstraints = true; |
| public final int simpleInliningConstraintThreshold = 0; |
| public boolean enableClassInlining = true; |
| public boolean enableClassStaticizer = true; |
| public boolean enableInitializedClassesAnalysis = true; |
| public boolean enableSideEffectAnalysis = true; |
| public boolean enableDeterminismAnalysis = true; |
| public boolean enableServiceLoaderRewriting = true; |
| public boolean enableNameReflectionOptimization = true; |
| public boolean enableStringConcatenationOptimization = true; |
| public boolean enableTreeShakingOfLibraryMethodOverrides = false; |
| public boolean encodeChecksums = false; |
| public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true; |
| public boolean forceAnnotateSynthetics = false; |
| public boolean readDebugSetFileEvent = false; |
| public boolean disableL8AnnotationRemoval = |
| System.getProperty("com.android.tools.r8.disableL8AnnotationRemoval") != null; |
| |
| public int callGraphLikelySpuriousCallEdgeThreshold = 50; |
| |
| public int verificationSizeLimitInBytes() { |
| if (testing.verificationSizeLimitInBytesOverride > -1) { |
| return testing.verificationSizeLimitInBytesOverride; |
| } |
| // For CF we use the defined limit in the spec. For DEX we use the limit of the static verifier |
| // https://android.googlesource.com/platform/art/+/android10-release/compiler/compiler.cc#48 |
| return isGeneratingClassFiles() ? 65534 : 16383; |
| } |
| |
| public int minimumVerificationSizeLimitInBytes() { |
| if (testing.verificationSizeLimitInBytesOverride > -1) { |
| return testing.verificationSizeLimitInBytesOverride; |
| } |
| return 16383; |
| } |
| |
| // We assume options will always be created on the main thread. |
| public Thread mainThread = Thread.currentThread(); |
| |
| public boolean enableSwitchRewriting = true; |
| public boolean enableStringSwitchConversion = true; |
| public int minimumStringSwitchSize = 3; |
| public boolean enableEnumValueOptimization = true; |
| public boolean enableEnumSwitchMapRemoval = true; |
| public final OutlineOptions outline = new OutlineOptions(); |
| public boolean enableInitializedClassesInInstanceMethodsAnalysis = true; |
| public boolean enableRedundantFieldLoadElimination = true; |
| // TODO(b/138917494): Disable until we have numbers on potential performance penalties. |
| public boolean enableRedundantConstNumberOptimization = false; |
| public boolean enableLoopUnrolling = true; |
| |
| // TODO(b/237567012): Remove when resolved. |
| public boolean enableCheckAllInstructionsDuringStackMapVerification = false; |
| |
| public String synthesizedClassPrefix = ""; |
| |
| // Number of threads to use while processing the dex files. |
| public int threadCount = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED; |
| // Print smali disassembly. |
| public boolean useSmaliSyntax = false; |
| // Verbose output. |
| public boolean verbose = false; |
| // Silencing output. |
| public boolean quiet = false; |
| // Throw exception if there is a warning about invalid debug info. |
| public boolean invalidDebugInfoFatal = false; |
| // Don't gracefully recover from invalid debug info. |
| public boolean invalidDebugInfoStrict = |
| System.getProperty("com.android.tools.r8.strictdebuginfo") != null; |
| |
| public boolean ignoreJavaLibraryOverride = false; |
| |
| // When dexsplitting we ignore main dex classes missing in the application. These will be |
| // fused together by play store when shipped for pre-L devices. |
| public boolean ignoreMainDexMissingClasses = false; |
| |
| // Boolean value indicating that byte code pass through may be enabled. |
| public boolean enableCfByteCodePassThrough = false; |
| |
| // Flag to control the representation of stateless lambdas. |
| // See b/222081665 for context. |
| public boolean createSingletonsForStatelessLambdas = |
| System.getProperty("com.android.tools.r8.createSingletonsForStatelessLambdas") != null; |
| |
| // Flag to allow record annotations in DEX. See b/231930852 for context. |
| public boolean emitRecordAnnotationsInDex = |
| System.getProperty("com.android.tools.r8.emitRecordAnnotationsInDex") != null; |
| |
| // Flag to allow nest annotations in DEX. See b/231930852 for context. |
| public boolean emitNestAnnotationsInDex = |
| System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null; |
| |
| // Flag to allow permitted subclasses annotations in DEX. See b/231930852 for context. |
| public boolean emitPermittedSubclassesAnnotationsInDex = |
| System.getProperty("com.android.tools.r8.emitPermittedSubclassesAnnotationsInDex") != null; |
| |
| private DumpInputFlags dumpInputFlags = DumpInputFlags.getDefault(); |
| |
| // Contain the contents of the build properties file from the compiler command. |
| public DumpOptions dumpOptions; |
| |
| // Hidden marker for classes.dex |
| private boolean hasMarker = false; |
| private Marker marker; |
| |
| public void setMarker(Marker marker) { |
| this.hasMarker = true; |
| this.marker = marker; |
| } |
| |
| public Marker getMarker(Tool tool) { |
| if (hasMarker) { |
| return marker; |
| } |
| return createMarker(tool); |
| } |
| |
| // Compute the marker to be placed in the main dex file. |
| private Marker createMarker(Tool tool) { |
| Marker marker = |
| new Marker(tool) |
| .setVersion(Version.LABEL) |
| .setCompilationMode(debug ? CompilationMode.DEBUG : CompilationMode.RELEASE) |
| .setBackend(isGeneratingClassFiles() ? Backend.CF : Backend.DEX) |
| .setHasChecksums(encodeChecksums); |
| // The marker records the min API if any desugaring happens or if the compiler generates dex |
| // since the output depends on the min API in this case. There is basically no min API entry |
| // in R8 cf to cf. |
| if (isGeneratingDex() || desugarState == DesugarState.ON) { |
| marker.setMinApi(getMinApiLevel().getLevel()); |
| } |
| if (machineDesugaredLibrarySpecification.getIdentifier() != null) { |
| marker.setDesugaredLibraryIdentifiers(machineDesugaredLibrarySpecification.getIdentifier()); |
| } |
| if (Version.isDevelopmentVersion()) { |
| marker.setSha1(VersionProperties.INSTANCE.getSha()); |
| } |
| if (tool == Tool.R8) { |
| marker.setR8Mode(forceProguardCompatibility ? "compatibility" : "full"); |
| } |
| if (androidPlatformBuild) { |
| marker.setAndroidPlatformBuild(); |
| } |
| return marker; |
| } |
| |
| public void setDumpInputFlags(DumpInputFlags dumpInputFlags) { |
| this.dumpInputFlags = dumpInputFlags; |
| } |
| |
| public boolean hasConsumer() { |
| return programConsumer != null; |
| } |
| |
| public InternalOutputMode getInternalOutputMode() { |
| assert hasConsumer(); |
| if (isGeneratingDexIndexed()) { |
| return InternalOutputMode.DexIndexed; |
| } else if (isGeneratingDexFilePerClassFile()) { |
| return InternalOutputMode.DexFilePerClassFile; |
| } else if (isGeneratingClassFiles()) { |
| return InternalOutputMode.ClassFile; |
| } |
| throw new UnsupportedOperationException("Cannot find internal output mode."); |
| } |
| |
| public boolean hasGlobalSyntheticsConsumer() { |
| return globalSyntheticsConsumer != null; |
| } |
| |
| public GlobalSyntheticsConsumer getGlobalSyntheticsConsumer() { |
| return globalSyntheticsConsumer; |
| } |
| |
| public void setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalSyntheticsConsumer) { |
| this.globalSyntheticsConsumer = globalSyntheticsConsumer; |
| } |
| |
| public boolean isDesugaredLibraryCompilation() { |
| return machineDesugaredLibrarySpecification.isLibraryCompilation(); |
| } |
| |
| public boolean isRelocatorCompilation() { |
| return relocatorCompilation; |
| } |
| |
| public boolean shouldKeepStackMapTable() { |
| assert isRelocatorCompilation() || getProguardConfiguration() != null; |
| return isRelocatorCompilation() || getProguardConfiguration().getKeepAttributes().stackMapTable; |
| } |
| |
| public boolean shouldRerunEnqueuer() { |
| return isShrinking() || isMinifying() || getProguardConfiguration().hasApplyMappingFile(); |
| } |
| |
| public boolean isGeneratingDex() { |
| return isGeneratingDexIndexed() || isGeneratingDexFilePerClassFile(); |
| } |
| |
| public boolean isGeneratingDexIndexed() { |
| return programConsumer instanceof DexIndexedConsumer; |
| } |
| |
| public boolean isGeneratingDexFilePerClassFile() { |
| return programConsumer instanceof DexFilePerClassFileConsumer; |
| } |
| |
| public boolean isGeneratingClassFiles() { |
| return programConsumer instanceof ClassFileConsumer; |
| } |
| |
| public boolean isDesugaring() { |
| return desugarState.isOn(); |
| } |
| |
| public boolean isCfDesugaring() { |
| return isGeneratingClassFiles() && desugarState.isOn(); |
| } |
| |
| public DexIndexedConsumer getDexIndexedConsumer() { |
| return (DexIndexedConsumer) programConsumer; |
| } |
| |
| public DexFilePerClassFileConsumer getDexFilePerClassFileConsumer() { |
| return (DexFilePerClassFileConsumer) programConsumer; |
| } |
| |
| public ClassFileConsumer getClassFileConsumer() { |
| return (ClassFileConsumer) programConsumer; |
| } |
| |
| public void signalFinishedToConsumers() { |
| if (programConsumer != null) { |
| programConsumer.finished(reporter); |
| if (dataResourceConsumer != null) { |
| dataResourceConsumer.finished(reporter); |
| } |
| } |
| if (featureSplitConfiguration != null) { |
| for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) { |
| ProgramConsumer programConsumer = featureSplit.getProgramConsumer(); |
| programConsumer.finished(reporter); |
| DataResourceConsumer dataResourceConsumer = programConsumer.getDataResourceConsumer(); |
| if (dataResourceConsumer != null) { |
| dataResourceConsumer.finished(reporter); |
| } |
| } |
| } |
| if (desugarGraphConsumer != null) { |
| desugarGraphConsumer.finished(); |
| } |
| } |
| |
| public boolean shouldDesugarNests() { |
| return !canUseNestBasedAccess(); |
| } |
| |
| public boolean shouldDesugarRecords() { |
| return desugarState.isOn() && !canUseRecords(); |
| } |
| |
| public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter(); |
| public Set<String> extensiveInterfaceMethodMinifierLoggingFilter = |
| getExtensiveInterfaceMethodMinifierLoggingFilter(); |
| |
| public List<String> methodsFilter = ImmutableList.of(); |
| private AndroidApiLevel minApiLevel = AndroidApiLevel.getDefault(); |
| // Skipping min_api check and compiling an intermediate result intended for later merging. |
| // Intermediate builds also emits or update synthesized classes mapping. |
| public boolean intermediate = false; |
| private boolean androidPlatformBuild = false; |
| public boolean retainCompileTimeAnnotations = true; |
| public boolean ignoreBootClasspathEnumsForMaindexTracing = |
| System.getProperty("com.android.tools.r8.ignoreBootClasspathEnumsForMaindexTracing") != null; |
| public boolean pruneNonVissibleAnnotationClasses = |
| System.getProperty("com.android.tools.r8.pruneNonVissibleAnnotationClasses") != null; |
| public List<String> logArgumentsFilter = ImmutableList.of(); |
| |
| // Flag to turn on/offLoad/store optimization in the Cf back-end. |
| public boolean enableLoadStoreOptimization = true; |
| // Flag to turn on/off desugaring in D8/R8. |
| public DesugarState desugarState = DesugarState.ON; |
| // Flag to turn on/off backport methods. |
| public boolean enableBackportMethods = true; |
| // Flag to turn on/off reduction of nest to improve class merging optimizations. |
| public boolean enableNestReduction = true; |
| // Defines interface method rewriter behavior. |
| public OffOrAuto interfaceMethodDesugaring = OffOrAuto.Auto; |
| // Defines try-with-resources rewriter behavior. |
| public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Auto; |
| // Flag to turn on/off processing of @dalvik.annotation.codegen.CovariantReturnType and |
| // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes. |
| public boolean processCovariantReturnTypeAnnotations = true; |
| // Flag to control library/program class lookup order. |
| // TODO(120884788): Enable this flag as the default. |
| public boolean lookupLibraryBeforeProgram = false; |
| // TODO(120884788): Leave this system property as a stop-gap for some time. |
| // public boolean lookupLibraryBeforeProgram = |
| // System.getProperty("com.android.tools.r8.lookupProgramBeforeLibrary") == null; |
| |
| public boolean enableEnqueuerDeferredTracing = |
| System.getProperty("com.android.tools.r8.disableEnqueuerDeferredTracing") == null; |
| |
| public boolean loadAllClassDefinitions = false; |
| |
| // Whether or not to check for valid multi-dex builds. |
| // |
| // For min-api levels that did not support native multi-dex the user should provide a main dex |
| // list. However, DX, didn't check that this was the case. Therefore, for CompatDX we have a flag |
| // to disable the check that the build makes sense for multi-dexing. |
| public boolean enableMainDexListCheck = true; |
| |
| private final boolean enableTreeShaking; |
| private final boolean enableMinification; |
| |
| public AndroidApiLevel getMinApiLevel() { |
| // If compiling to CF with no desugaring then we should not inspect the min-api. |
| // For now we assert the API level for non-desugared CF is B, but it would be better to never |
| // access the min-api in those cases. |
| assert desugarState.isOn() || isGeneratingDex() || minApiLevel.equals(AndroidApiLevel.B); |
| return minApiLevel; |
| } |
| |
| public void setMinApiLevel(AndroidApiLevel minApiLevel) { |
| assert minApiLevel != null; |
| this.minApiLevel = minApiLevel; |
| } |
| |
| public boolean isOptimizing() { |
| return hasProguardConfiguration() && getProguardConfiguration().isOptimizing(); |
| } |
| |
| public boolean isRelease() { |
| return !debug; |
| } |
| |
| public boolean isShrinking() { |
| assert proguardConfiguration == null |
| || enableTreeShaking == proguardConfiguration.isShrinking(); |
| return enableTreeShaking; |
| } |
| |
| public boolean isMinifying() { |
| assert proguardConfiguration == null |
| || enableMinification == proguardConfiguration.isObfuscating(); |
| return enableMinification; |
| } |
| |
| @Override |
| public boolean isAnnotationRemovalEnabled() { |
| return !isForceProguardCompatibilityEnabled(); |
| } |
| |
| @Override |
| public boolean isTreeShakingEnabled() { |
| return isShrinking(); |
| } |
| |
| @Override |
| public boolean isMinificationEnabled() { |
| return isMinifying(); |
| } |
| |
| @Override |
| public boolean isOptimizationEnabled() { |
| return isOptimizing(); |
| } |
| |
| @Override |
| public boolean isRepackagingEnabled() { |
| return !debug |
| && proguardConfiguration != null |
| && proguardConfiguration.getPackageObfuscationMode().isSome() |
| && (isMinifying() || !isForceProguardCompatibilityEnabled()); |
| } |
| |
| @Override |
| public boolean isForceProguardCompatibilityEnabled() { |
| return forceProguardCompatibility; |
| } |
| |
| public boolean parseSignatureAttribute() { |
| return isKeepAttributesSignatureEnabled(); |
| } |
| |
| @Override |
| public boolean isKeepAttributesSignatureEnabled() { |
| return proguardConfiguration == null || proguardConfiguration.getKeepAttributes().signature; |
| } |
| |
| @Override |
| public boolean isKeepEnclosingMethodAttributeEnabled() { |
| return proguardConfiguration.getKeepAttributes().enclosingMethod; |
| } |
| |
| @Override |
| public boolean isKeepInnerClassesAttributeEnabled() { |
| return proguardConfiguration.getKeepAttributes().innerClasses; |
| } |
| |
| @Override |
| public boolean isKeepRuntimeInvisibleAnnotationsEnabled() { |
| return proguardConfiguration.getKeepAttributes().runtimeInvisibleAnnotations; |
| } |
| |
| @Override |
| public boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled() { |
| return proguardConfiguration.getKeepAttributes().runtimeInvisibleParameterAnnotations; |
| } |
| |
| @Override |
| public boolean isKeepRuntimeVisibleAnnotationsEnabled() { |
| return proguardConfiguration.getKeepAttributes().runtimeVisibleAnnotations; |
| } |
| |
| @Override |
| public boolean isKeepRuntimeVisibleParameterAnnotationsEnabled() { |
| return proguardConfiguration.getKeepAttributes().runtimeVisibleParameterAnnotations; |
| } |
| |
| /** |
| * If any non-static class merging is enabled, information about types referred to by instanceOf |
| * and check cast instructions needs to be collected. |
| */ |
| public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) { |
| if (mode.isInitialTreeShaking()) { |
| return (horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.INITIAL) |
| && !horizontalClassMergerOptions.isRestrictedToSynthetics()) |
| || enableVerticalClassMerging; |
| } |
| if (mode.isFinalTreeShaking()) { |
| return horizontalClassMergerOptions.isEnabled(HorizontalClassMerger.Mode.FINAL) |
| && !horizontalClassMergerOptions.isRestrictedToSynthetics(); |
| } |
| assert false; |
| return false; |
| } |
| |
| @Override |
| public boolean isAccessModificationEnabled() { |
| return getProguardConfiguration() != null |
| && getProguardConfiguration().isAccessModificationAllowed(); |
| } |
| |
| @Override |
| public boolean isMethodStaticizingEnabled() { |
| return callSiteOptimizationOptions().isMethodStaticizingEnabled(); |
| } |
| |
| public boolean keepInnerClassStructure() { |
| return getProguardConfiguration().getKeepAttributes().signature |
| || getProguardConfiguration().getKeepAttributes().innerClasses; |
| } |
| |
| public boolean canUseInputStackMaps() { |
| return testing.readInputStackMaps ? testing.readInputStackMaps : isGeneratingClassFiles(); |
| } |
| |
| public boolean printCfg = false; |
| public String printCfgFile; |
| public boolean ignoreMissingClasses = false; |
| public boolean reportMissingClassesInEnclosingMethodAttribute = false; |
| public boolean reportMissingClassesInInnerClassAttributes = false; |
| public boolean disableGenericSignatureValidation = false; |
| public boolean disableInnerClassSeparatorValidationWhenRepackaging = false; |
| |
| // EXPERIMENTAL flag to get behaviour as close to Proguard as possible. |
| public boolean forceProguardCompatibility = false; |
| public AssertionConfigurationWithDefault assertionsConfiguration = null; |
| public boolean configurationDebugging = false; |
| |
| // Don't convert Code objects to IRCode. |
| public boolean skipIR = false; |
| |
| public boolean debug = false; |
| |
| private final CallSiteOptimizationOptions callSiteOptimizationOptions = |
| new CallSiteOptimizationOptions(); |
| private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions(); |
| private final ClassInlinerOptions classInlinerOptions = new ClassInlinerOptions(); |
| private final InlinerOptions inlinerOptions = new InlinerOptions(); |
| private final HorizontalClassMergerOptions horizontalClassMergerOptions = |
| new HorizontalClassMergerOptions(); |
| private final OpenClosedInterfacesOptions openClosedInterfacesOptions = |
| new OpenClosedInterfacesOptions(); |
| private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions(); |
| private final KotlinOptimizationOptions kotlinOptimizationOptions = |
| new KotlinOptimizationOptions(); |
| private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions(); |
| private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions(); |
| private final MappingComposeOptions mappingComposeOptions = new MappingComposeOptions(); |
| private final ArtProfileOptions artProfileOptions = new ArtProfileOptions(); |
| private final StartupOptions startupOptions = new StartupOptions(); |
| private final StartupInstrumentationOptions startupInstrumentationOptions = |
| new StartupInstrumentationOptions(); |
| public final TestingOptions testing = new TestingOptions(); |
| |
| public List<ProguardConfigurationRule> mainDexKeepRules = ImmutableList.of(); |
| public boolean minimalMainDex; |
| /** |
| * Enable usage of InheritanceClassInDexDistributor for multidex legacy builds. This allows |
| * distribution of classes to minimize DexOpt LinearAlloc usage by minimizing linking errors |
| * during DexOpt and controlling the load of classes with linking issues. This has the consequence |
| * of making minimal main dex not absolutely minimal regarding runtime execution constraints |
| * because it's adding classes in the main dex to satisfy also DexOpt constraints. |
| */ |
| public boolean enableInheritanceClassInDexDistributor = true; |
| |
| public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON; |
| |
| public CallSiteOptimizationOptions callSiteOptimizationOptions() { |
| return callSiteOptimizationOptions; |
| } |
| |
| public ClassInlinerOptions classInlinerOptions() { |
| return classInlinerOptions; |
| } |
| |
| public InlinerOptions inlinerOptions() { |
| return inlinerOptions; |
| } |
| |
| public HorizontalClassMergerOptions horizontalClassMergerOptions() { |
| return horizontalClassMergerOptions; |
| } |
| |
| public ProtoShrinkingOptions protoShrinking() { |
| return protoShrinking; |
| } |
| |
| public KotlinOptimizationOptions kotlinOptimizationOptions() { |
| return kotlinOptimizationOptions; |
| } |
| |
| public ApiModelTestingOptions apiModelingOptions() { |
| return apiModelTestingOptions; |
| } |
| |
| public MappingComposeOptions mappingComposeOptions() { |
| return mappingComposeOptions; |
| } |
| |
| public DesugarSpecificOptions desugarSpecificOptions() { |
| return desugarSpecificOptions; |
| } |
| |
| public CfCodeAnalysisOptions getCfCodeAnalysisOptions() { |
| return cfCodeAnalysisOptions; |
| } |
| |
| public DumpInputFlags getDumpInputFlags() { |
| return dumpInputFlags; |
| } |
| |
| public OpenClosedInterfacesOptions getOpenClosedInterfacesOptions() { |
| return openClosedInterfacesOptions; |
| } |
| |
| public ArtProfileOptions getArtProfileOptions() { |
| return artProfileOptions; |
| } |
| |
| public StartupOptions getStartupOptions() { |
| return startupOptions; |
| } |
| |
| public StartupInstrumentationOptions getStartupInstrumentationOptions() { |
| return startupInstrumentationOptions; |
| } |
| |
| public TestingOptions getTestingOptions() { |
| return testing; |
| } |
| |
| private static Set<String> getExtensiveLoggingFilter() { |
| String property = System.getProperty("com.android.tools.r8.extensiveLoggingFilter"); |
| if (property != null) { |
| ImmutableSet.Builder<String> builder = ImmutableSet.builder(); |
| for (String method : property.split(";")) { |
| builder.add(method); |
| } |
| return builder.build(); |
| } |
| return ImmutableSet.of(); |
| } |
| |
| private static Set<String> getExtensiveInterfaceMethodMinifierLoggingFilter() { |
| String property = |
| System.getProperty("com.android.tools.r8.extensiveInterfaceMethodMinifierLoggingFilter"); |
| if (property != null) { |
| ImmutableSet.Builder<String> builder = ImmutableSet.builder(); |
| for (String method : property.split(";")) { |
| builder.add(method); |
| } |
| return builder.build(); |
| } |
| return ImmutableSet.of(); |
| } |
| |
| public static class InvalidParameterAnnotationInfo { |
| |
| final DexMethod method; |
| final int expectedParameterCount; |
| final int actualParameterCount; |
| |
| public InvalidParameterAnnotationInfo( |
| DexMethod method, int expectedParameterCount, int actualParameterCount) { |
| this.method = method; |
| this.expectedParameterCount = expectedParameterCount; |
| this.actualParameterCount = actualParameterCount; |
| } |
| } |
| |
| private static class TypeVersionPair { |
| |
| final CfVersion version; |
| final DexType type; |
| |
| public TypeVersionPair(CfVersion version, DexType type) { |
| this.version = version; |
| this.type = type; |
| } |
| } |
| |
| private final Map<Origin, List<TypeVersionPair>> missingEnclosingMembers = new HashMap<>(); |
| |
| private final Map<Origin, List<InvalidParameterAnnotationInfo>> warningInvalidParameterAnnotations |
| = new HashMap<>(); |
| |
| private final Map<Origin, List<Pair<ProgramMethod, String>>> warningInvalidDebugInfo = |
| new HashMap<>(); |
| |
| // Don't read code from dex files. Used to extract non-code information from vdex files where |
| // the code contains unsupported byte codes. |
| public boolean skipReadingDexCode = false; |
| |
| // If null, no main-dex list needs to be computed. |
| // If non null it must be and passed to the consumer. |
| public StringConsumer mainDexListConsumer = null; |
| |
| // If null, no proguard map needs to be computed. |
| // If non null it must be and passed to the consumer. |
| public StringConsumer proguardMapConsumer = null; |
| |
| // If null, no usage information needs to be computed. |
| // If non-null, it must be and is passed to the consumer. |
| public StringConsumer usageInformationConsumer = null; |
| |
| public boolean hasUsageInformationConsumer() { |
| return usageInformationConsumer != null; |
| } |
| |
| // If null, no proguard seeds info needs to be computed. |
| // If non null it must be and passed to the consumer. |
| public StringConsumer proguardSeedsConsumer = null; |
| |
| // If null, no configuration information needs to be printed. |
| // If non-null, configuration must be passed to the consumer. |
| public StringConsumer configurationConsumer = null; |
| |
| public void setDesugaredLibrarySpecification(DesugaredLibrarySpecification specification) { |
| if (specification.isEmpty()) { |
| return; |
| } |
| loadMachineDesugaredLibrarySpecification = |
| (timing, app) -> |
| machineDesugaredLibrarySpecification = |
| specification.toMachineSpecification(app, timing); |
| } |
| |
| private ThrowingBiConsumer<Timing, DexApplication, IOException> |
| loadMachineDesugaredLibrarySpecification = null; |
| |
| public void loadMachineDesugaredLibrarySpecification(Timing timing, DexApplication app) |
| throws IOException { |
| if (loadMachineDesugaredLibrarySpecification == null) { |
| return; |
| } |
| timing.begin("Load machine specification"); |
| loadMachineDesugaredLibrarySpecification.accept(timing, app); |
| timing.end(); |
| } |
| |
| // Contains flags describing library desugaring. |
| public MachineDesugaredLibrarySpecification machineDesugaredLibrarySpecification = |
| MachineDesugaredLibrarySpecification.empty(); |
| |
| public TypeRewriter getTypeRewriter() { |
| return machineDesugaredLibrarySpecification.requiresTypeRewriting() |
| ? new MachineDesugarPrefixRewritingMapper(machineDesugaredLibrarySpecification) |
| : TypeRewriter.empty(); |
| } |
| |
| public boolean relocatorCompilation = false; |
| |
| // If null, no keep rules are recorded. |
| // If non null it records desugared library APIs used by the program. |
| public StringConsumer desugaredLibraryKeepRuleConsumer = null; |
| |
| // If null, no graph information needs to be provided for the keep/inclusion of classes |
| // in the output. If non-null, each edge pertaining to kept parts of the resulting program |
| // must be reported to the consumer. |
| public GraphConsumer keptGraphConsumer = null; |
| |
| // If null, no graph information needs to be provided for the keep/inclusion of classes |
| // in the main-dex output. If non-null, each edge pertaining to kept parts in the main-dex output |
| // of the resulting program must be reported to the consumer. |
| public GraphConsumer mainDexKeptGraphConsumer = null; |
| |
| // If null, no desugaring dependencies need to be provided. If non-null, each dependency between |
| // code objects needed for correct desugaring needs to be provided to the consumer. |
| public DesugarGraphConsumer desugarGraphConsumer = null; |
| |
| public Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null; |
| |
| public MapIdProvider mapIdProvider = null; |
| public SourceFileProvider sourceFileProvider = null; |
| |
| public static boolean assertionsEnabled() { |
| boolean assertionsEnabled = false; |
| assert assertionsEnabled = true; // Intentional side-effect. |
| return assertionsEnabled; |
| } |
| |
| public static void checkAssertionsEnabled() { |
| if (!assertionsEnabled()) { |
| throw new Unreachable(); |
| } |
| } |
| |
| /** A set of dexitems we have reported missing to dedupe warnings. */ |
| private final Set<DexItem> reportedMissingForDesugaring = Sets.newConcurrentHashSet(); |
| |
| private final AtomicBoolean reportedErrorReadingKotlinMetadataReflectively = |
| new AtomicBoolean(false); |
| private final Set<DexItem> invalidLibraryClasses = Sets.newConcurrentHashSet(); |
| |
| public RuntimeException errorMissingNestHost(DexClass clazz) { |
| throw reporter.fatalError( |
| new MissingNestHostNestDesugarDiagnostic( |
| clazz.getOrigin(), Position.UNKNOWN, messageErrorMissingNestHost(clazz))); |
| } |
| |
| private static String messageErrorMissingNestHost(DexClass compiledClass) { |
| String nestHostName = compiledClass.getNestHost().getName(); |
| return "Class " |
| + compiledClass.type.getName() |
| + " requires its nest host " |
| + nestHostName |
| + " to be on program or class path."; |
| } |
| |
| public RuntimeException errorMissingNestMember(Nest nest) { |
| throw reporter.fatalError( |
| new IncompleteNestNestDesugarDiagnosic( |
| nest.getHostClass().getOrigin(), Position.UNKNOWN, messageErrorIncompleteNest(nest))); |
| } |
| |
| private static String messageErrorIncompleteNest(Nest nest) { |
| List<DexProgramClass> programClassesFromNest = new ArrayList<>(); |
| List<DexClasspathClass> classpathClassesFromNest = new ArrayList<>(); |
| List<DexLibraryClass> libraryClassesFromNest = new ArrayList<>(); |
| nest.getHostClass() |
| .accept( |
| programClassesFromNest::add, |
| classpathClassesFromNest::add, |
| libraryClassesFromNest::add); |
| for (DexClass memberClass : nest.getMembers()) { |
| memberClass.accept( |
| programClassesFromNest::add, classpathClassesFromNest::add, libraryClassesFromNest::add); |
| } |
| StringBuilder stringBuilder = |
| new StringBuilder("Compilation of classes ") |
| .append(StringUtils.join(", ", programClassesFromNest, DexClass::getTypeName)) |
| .append(" requires its nest mates "); |
| if (nest.hasMissingMembers()) { |
| stringBuilder |
| .append(StringUtils.join(", ", nest.getMissingMembers(), DexType::getTypeName)) |
| .append(" (unavailable) "); |
| } |
| if (!libraryClassesFromNest.isEmpty()) { |
| stringBuilder |
| .append(StringUtils.join(", ", libraryClassesFromNest, DexClass::getTypeName)) |
| .append(" (on library path) "); |
| } |
| stringBuilder.append("to be on program or class path."); |
| if (!classpathClassesFromNest.isEmpty()) { |
| stringBuilder |
| .append("(Classes ") |
| .append(StringUtils.join(", ", classpathClassesFromNest, DexClass::getTypeName)) |
| .append(" from the same nest are on class path)."); |
| } |
| return stringBuilder.toString(); |
| } |
| |
| public void warningMissingTypeForDesugar( |
| Origin origin, Position position, DexType missingType, DexMethod context) { |
| if (reportedMissingForDesugaring.add(missingType)) { |
| reporter.warning( |
| new InterfaceDesugarMissingTypeDiagnostic( |
| origin, |
| position, |
| Reference.classFromDescriptor(missingType.toDescriptorString()), |
| Reference.classFromDescriptor(context.holder.toDescriptorString()), |
| null)); |
| } |
| } |
| |
| public void warningMissingInterfaceForDesugar( |
| DexClass classToDesugar, DexClass implementing, DexType missing) { |
| if (reportedMissingForDesugaring.add(missing)) { |
| reporter.warning( |
| new InterfaceDesugarMissingTypeDiagnostic( |
| classToDesugar.getOrigin(), |
| Position.UNKNOWN, |
| Reference.classFromDescriptor(missing.toDescriptorString()), |
| Reference.classFromDescriptor(classToDesugar.getType().toDescriptorString()), |
| classToDesugar == implementing |
| ? null |
| : Reference.classFromDescriptor(implementing.getType().toDescriptorString()))); |
| } |
| } |
| |
| public void warningReadingKotlinMetadataReflective() { |
| if (reportedErrorReadingKotlinMetadataReflectively.compareAndSet(false, true)) { |
| reporter.warning( |
| new StringDiagnostic( |
| "Could not read the kotlin metadata message reflectively which indicates the" |
| + " compiler running in the context of a Security Manager. Not being able to" |
| + " read the kotlin metadata will have a negative effect oncode size")); |
| } |
| } |
| |
| public void warningInvalidLibrarySuperclassForDesugar( |
| Origin origin, |
| DexType libraryType, |
| DexType invalidSuperType, |
| String message, |
| Set<DexMethod> retarget) { |
| if (invalidLibraryClasses.add(invalidSuperType)) { |
| reporter.warning( |
| new InvalidLibrarySuperclassDiagnostic( |
| origin, |
| Reference.classFromDescriptor(libraryType.toDescriptorString()), |
| Reference.classFromDescriptor(invalidSuperType.toDescriptorString()), |
| message, |
| Lists.newArrayList( |
| Iterables.transform(retarget, method -> method.asMethodReference())))); |
| } |
| } |
| |
| public void warningMissingEnclosingMember(DexType clazz, Origin origin, CfVersion version) { |
| TypeVersionPair pair = new TypeVersionPair(version, clazz); |
| synchronized (missingEnclosingMembers) { |
| missingEnclosingMembers.computeIfAbsent(origin, k -> new ArrayList<>()).add(pair); |
| } |
| } |
| |
| public void warningInvalidParameterAnnotations( |
| DexMethod method, Origin origin, int expected, int actual) { |
| InvalidParameterAnnotationInfo info = |
| new InvalidParameterAnnotationInfo(method, expected, actual); |
| synchronized (warningInvalidParameterAnnotations) { |
| warningInvalidParameterAnnotations.computeIfAbsent(origin, k -> new ArrayList<>()).add(info); |
| } |
| } |
| |
| public void warningInvalidDebugInfo( |
| ProgramMethod method, Origin origin, InvalidDebugInfoException e) { |
| if (invalidDebugInfoFatal) { |
| throw new CompilationError("Fatal warning: Invalid debug info", e); |
| } |
| synchronized (warningInvalidDebugInfo) { |
| warningInvalidDebugInfo.computeIfAbsent( |
| origin, k -> new ArrayList<>()).add(new Pair<>(method, e.getMessage())); |
| } |
| } |
| |
| public boolean printWarnings() { |
| boolean printed = false; |
| boolean printOutdatedToolchain = false; |
| if (warningInvalidParameterAnnotations.size() > 0) { |
| // TODO(b/67626202): Add a regression test with a program that hits this issue. |
| reporter.info( |
| new StringDiagnostic( |
| "Invalid parameter counts in MethodParameter attributes. " |
| + "This is likely due to Proguard having removed a parameter.")); |
| for (Origin origin : new TreeSet<>(warningInvalidParameterAnnotations.keySet())) { |
| StringBuilder builder = |
| new StringBuilder("Methods with invalid MethodParameter attributes:"); |
| for (InvalidParameterAnnotationInfo info : warningInvalidParameterAnnotations.get(origin)) { |
| builder |
| .append("\n ") |
| .append(info.method) |
| .append(" expected count: ") |
| .append(info.expectedParameterCount) |
| .append(" actual count: ") |
| .append(info.actualParameterCount); |
| } |
| reporter.info(new StringDiagnostic(builder.toString(), origin)); |
| } |
| printed = true; |
| } |
| if (warningInvalidDebugInfo.size() > 0) { |
| int count = 0; |
| for (List<Pair<ProgramMethod, String>> methods : warningInvalidDebugInfo.values()) { |
| count += methods.size(); |
| } |
| reporter.info( |
| new StringDiagnostic( |
| "Stripped invalid locals information from " |
| + count |
| + (count == 1 ? " method." : " methods."))); |
| for (Origin origin : new TreeSet<>(warningInvalidDebugInfo.keySet())) { |
| StringBuilder builder = new StringBuilder("Methods with invalid locals information:"); |
| for (Pair<ProgramMethod, String> method : warningInvalidDebugInfo.get(origin)) { |
| builder.append("\n ").append(method.getFirst().toSourceString()); |
| builder.append("\n ").append(method.getSecond()); |
| } |
| reporter.info(new StringDiagnostic(builder.toString(), origin)); |
| } |
| printed = true; |
| printOutdatedToolchain = true; |
| } |
| if (missingEnclosingMembers.size() > 0) { |
| reporter.info( |
| new StringDiagnostic( |
| "InnerClasses attribute has entries missing a corresponding " |
| + "EnclosingMethod attribute. " |
| + "Such InnerClasses attribute entries are ignored.")); |
| for (Origin origin : new TreeSet<>(missingEnclosingMembers.keySet())) { |
| StringBuilder builder = new StringBuilder("Classes with missing EnclosingMethod: "); |
| boolean first = true; |
| for (TypeVersionPair pair : missingEnclosingMembers.get(origin)) { |
| if (first) { |
| first = false; |
| } else { |
| builder.append(", "); |
| } |
| builder.append(pair.type); |
| printOutdatedToolchain |= pair.version.isLessThan(CfVersion.V1_5); |
| } |
| reporter.info(new StringDiagnostic(builder.toString(), origin)); |
| } |
| printed = true; |
| } |
| if (printOutdatedToolchain) { |
| reporter.info( |
| new StringDiagnostic( |
| "Some warnings are typically a sign of using an outdated Java toolchain." |
| + " To fix, recompile the source with an updated toolchain.")); |
| } |
| return printed; |
| } |
| |
| public boolean hasMethodsFilter() { |
| return methodsFilter.size() > 0; |
| } |
| |
| public boolean methodMatchesFilter(DexEncodedMethod method) { |
| // Not specifying a filter matches all methods. |
| if (!hasMethodsFilter()) { |
| return true; |
| } |
| // Currently the filter is simple string equality on the qualified name. |
| String qualifiedName = method.qualifiedName(); |
| return methodsFilter.contains(qualifiedName); |
| } |
| |
| public boolean methodMatchesLogArgumentsFilter(DexEncodedMethod method) { |
| // Not specifying a filter matches no methods. |
| if (logArgumentsFilter.size() == 0) { |
| return false; |
| } |
| // Currently the filter is simple string equality on the qualified name. |
| String qualifiedName = method.qualifiedName(); |
| return logArgumentsFilter.contains(qualifiedName); |
| } |
| |
| public enum PackageObfuscationMode { |
| // No package obfuscation. |
| NONE, |
| // Strategy based on ordinary package obfuscation when no package-obfuscation mode is specified |
| // by the users. In practice this falls back to FLATTEN but with keeping package-names. |
| MINIFICATION, |
| // Repackaging all classes into the single user-given (or top-level) package. |
| REPACKAGE, |
| // Repackaging all packages into the single user-given (or top-level) package. |
| FLATTEN; |
| |
| public boolean isNone() { |
| return this == NONE; |
| } |
| |
| public boolean isFlattenPackageHierarchy() { |
| return this == FLATTEN; |
| } |
| |
| public boolean isRepackageClasses() { |
| return this == REPACKAGE; |
| } |
| |
| public boolean isMinification() { |
| return this == MINIFICATION; |
| } |
| |
| public boolean isSome() { |
| return !isNone(); |
| } |
| } |
| |
| public static class OutlineOptions { |
| public boolean enabled = true; |
| public int minSize = 3; |
| public int maxSize = 99; |
| public int threshold = 20; |
| public int maxNumberOfInstructionsToBeConsidered = 100; |
| } |
| |
| public static class KotlinOptimizationOptions { |
| public boolean disableKotlinSpecificOptimizations = |
| System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null; |
| } |
| |
| // Temporary desugar specific options to make progress on b/147485959 |
| // All options should be including bugs to either fix the underlying issue or extend the api. |
| public static class DesugarSpecificOptions { |
| // b/172508621 |
| public boolean sortMethodsOnCfOutput = |
| System.getProperty("com.android.tools.r8.sortMethodsOnCfWriting") != null; |
| // Desugaring is not fully idempotent. With this option turned on all desugared input is |
| // allowed, and if it is detected that the desugared input cannot be reprocessed, that input |
| // will be passed-through without the problematic rewritings applied. |
| public boolean allowAllDesugaredInput = |
| System.getProperty("com.android.tools.r8.allowAllDesugaredInput") != null; |
| // See b/191469661 for why this is here. |
| public boolean noCfMarkerForDesugaredCode = |
| System.getProperty("com.android.tools.r8.noCfMarkerForDesugaredCode") != null; |
| // See b/182065081 for why this is here. |
| public boolean lambdaClassFieldsFinal = |
| System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null; |
| } |
| |
| public class CallSiteOptimizationOptions { |
| |
| private boolean enabled = true; |
| private boolean enableMethodStaticizing = true; |
| |
| private boolean forceSyntheticsForInstanceInitializers = false; |
| |
| public void disableOptimization() { |
| enabled = false; |
| } |
| |
| public int getMaxNumberOfInParameters() { |
| return 10; |
| } |
| |
| public boolean isEnabled() { |
| if (!isOptimizing() || !isShrinking()) { |
| return false; |
| } |
| return enabled; |
| } |
| |
| public boolean isForceSyntheticsForInstanceInitializersEnabled() { |
| return forceSyntheticsForInstanceInitializers; |
| } |
| |
| public boolean isMethodStaticizingEnabled() { |
| return enableMethodStaticizing; |
| } |
| |
| public CallSiteOptimizationOptions setEnabled(boolean enabled) { |
| if (enabled) { |
| assert isEnabled(); |
| } else { |
| disableOptimization(); |
| } |
| return this; |
| } |
| |
| public CallSiteOptimizationOptions setForceSyntheticsForInstanceInitializers( |
| boolean forceSyntheticsForInstanceInitializers) { |
| this.forceSyntheticsForInstanceInitializers = forceSyntheticsForInstanceInitializers; |
| return this; |
| } |
| |
| public CallSiteOptimizationOptions setEnableMethodStaticizing(boolean enableMethodStaticizing) { |
| this.enableMethodStaticizing = enableMethodStaticizing; |
| return this; |
| } |
| } |
| |
| public static class CfCodeAnalysisOptions { |
| |
| private boolean allowUnreachableCfBlocks = true; |
| private boolean enableUnverifiableCodeReporting = false; |
| |
| public boolean isUnverifiableCodeReportingEnabled() { |
| return enableUnverifiableCodeReporting; |
| } |
| |
| public boolean isUnreachableCfBlocksAllowed() { |
| return allowUnreachableCfBlocks; |
| } |
| |
| public CfCodeAnalysisOptions setAllowUnreachableCfBlocks(boolean allowUnreachableCfBlocks) { |
| this.allowUnreachableCfBlocks = allowUnreachableCfBlocks; |
| return this; |
| } |
| |
| public CfCodeAnalysisOptions setEnableUnverifiableCodeReporting( |
| boolean enableUnverifiableCodeReporting) { |
| this.enableUnverifiableCodeReporting = enableUnverifiableCodeReporting; |
| return this; |
| } |
| } |
| |
| public class ClassInlinerOptions { |
| |
| public int classInliningInstructionAllowance = -1; |
| |
| public int getClassInliningInstructionAllowance() { |
| if (classInliningInstructionAllowance >= 0) { |
| return classInliningInstructionAllowance; |
| } |
| if (isGeneratingClassFiles()) { |
| return 50; |
| } |
| assert isGeneratingDex(); |
| return 65; |
| } |
| } |
| |
| public interface ApplyInliningToInlineePredicate { |
| |
| boolean test(AppView<?> appView, ProgramMethod method, int inliningDepth); |
| } |
| |
| public class InlinerOptions { |
| |
| public boolean enableInlining = |
| !parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.disableinlining", false); |
| |
| // This defines the limit of instructions in the inlinee |
| public int simpleInliningInstructionLimit = |
| parseSystemPropertyForDevelopmentOrDefault( |
| "com.android.tools.r8.inliningInstructionLimit", -1); |
| |
| public int[] multiCallerInliningInstructionLimits = |
| new int[] {Integer.MAX_VALUE, 28, 16, 12, 10}; |
| |
| // This defines how many instructions of inlinees we can inlinee overall. |
| public int inliningInstructionAllowance = 1500; |
| |
| // Maximum number of distinct values in a method that may be used in a monitor-enter |
| // instruction. |
| public int inliningMonitorEnterValuesAllowance = 4; |
| |
| // Maximum number of control flow resolution blocks that setup the register state before |
| // the actual catch handler allowed when inlining. Threshold found empirically by testing on |
| // GMS Core. |
| public int inliningControlFlowResolutionBlocksThreshold = 15; |
| |
| public boolean enableInliningOfInvokesWithClassInitializationSideEffects = true; |
| public boolean enableInliningOfInvokesWithNullableReceivers = true; |
| public boolean disableInliningOfLibraryMethodOverrides = true; |
| |
| public ApplyInliningToInlineePredicate applyInliningToInlineePredicateForTesting = null; |
| |
| public int getSimpleInliningInstructionLimit() { |
| // If a custom simple inlining instruction limit is set, then use that. |
| if (simpleInliningInstructionLimit >= 0) { |
| return simpleInliningInstructionLimit; |
| } |
| // Allow 3 instructions when generating to class files. |
| if (isGeneratingClassFiles()) { |
| return 3; |
| } |
| // Allow the size of the dex code to be up to 5 bytes. |
| assert isGeneratingDex(); |
| return 5; |
| } |
| |
| public boolean shouldApplyInliningToInlinee( |
| AppView<?> appView, ProgramMethod inlinee, int inliningDepth) { |
| if (applyInliningToInlineePredicateForTesting != null) { |
| return applyInliningToInlineePredicateForTesting.test(appView, inlinee, inliningDepth); |
| } |
| if (protoShrinking.shouldApplyInliningToInlinee(appView, inlinee, inliningDepth)) { |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| public class HorizontalClassMergerOptions { |
| |
| // TODO(b/138781768): Set enable to true when this bug is resolved. |
| private boolean enable = |
| !Version.isDevelopmentVersion() |
| || System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null; |
| // TODO(b/205611444): Enable by default. |
| private boolean enableClassInitializerDeadlockDetection = true; |
| private boolean enableInterfaceMerging = |
| System.getProperty("com.android.tools.r8.enableHorizontalInterfaceMerging") != null; |
| private boolean enableInterfaceMergingInInitial = false; |
| private boolean enableSameFilePolicy = |
| System.getProperty("com.android.tools.r8.enableSameFilePolicy") != null; |
| private boolean enableSyntheticMerging = true; |
| private boolean ignoreRuntimeTypeChecksForTesting = false; |
| private boolean restrictToSynthetics = false; |
| |
| public void disable() { |
| enable = false; |
| } |
| |
| public void disableSyntheticMerging() { |
| enableSyntheticMerging = false; |
| } |
| |
| public void enable() { |
| enable = true; |
| } |
| |
| public void enableIf(boolean enable) { |
| this.enable = enable; |
| } |
| |
| public int getMaxClassGroupSizeInR8() { |
| return 30; |
| } |
| |
| public int getMaxClassGroupSizeInD8() { |
| return 100; |
| } |
| |
| public int getMaxInterfaceGroupSize() { |
| return 100; |
| } |
| |
| public boolean isConstructorMergingEnabled() { |
| return true; |
| } |
| |
| public boolean isClassInitializerDeadlockDetectionEnabled() { |
| return enableClassInitializerDeadlockDetection; |
| } |
| |
| public boolean isEnabled(HorizontalClassMerger.Mode mode) { |
| if (!enable || debug || intermediate) { |
| return false; |
| } |
| if (mode.isInitial()) { |
| return inlinerOptions.enableInlining && isShrinking(); |
| } |
| assert mode.isFinal(); |
| return true; |
| } |
| |
| public boolean isIgnoreRuntimeTypeChecksForTestingEnabled() { |
| return ignoreRuntimeTypeChecksForTesting; |
| } |
| |
| public boolean isSameFilePolicyEnabled() { |
| return enableSameFilePolicy; |
| } |
| |
| public boolean isSyntheticMergingEnabled() { |
| return enableSyntheticMerging; |
| } |
| |
| public boolean isInterfaceMergingEnabled(HorizontalClassMerger.Mode mode) { |
| if (!enableInterfaceMerging) { |
| return false; |
| } |
| if (mode.isInitial()) { |
| return enableInterfaceMergingInInitial; |
| } |
| assert mode.isFinal(); |
| return true; |
| } |
| |
| public boolean isRestrictedToSynthetics() { |
| return restrictToSynthetics || !isOptimizing() || !isShrinking(); |
| } |
| |
| public void setEnableClassInitializerDeadlockDetection() { |
| enableClassInitializerDeadlockDetection = true; |
| } |
| |
| public void setEnableInterfaceMerging() { |
| enableInterfaceMerging = true; |
| } |
| |
| public void setEnableInterfaceMerging(boolean enableInterfaceMerging) { |
| this.enableInterfaceMerging = enableInterfaceMerging; |
| } |
| |
| public void setEnableInterfaceMergingInInitial() { |
| enableInterfaceMergingInInitial = true; |
| } |
| |
| public void setEnableSameFilePolicy(boolean enableSameFilePolicy) { |
| this.enableSameFilePolicy = enableSameFilePolicy; |
| } |
| |
| public void setIgnoreRuntimeTypeChecksForTesting() { |
| ignoreRuntimeTypeChecksForTesting = true; |
| } |
| |
| public void setRestrictToSynthetics() { |
| restrictToSynthetics = true; |
| } |
| } |
| |
| public static class OpenClosedInterfacesOptions { |
| |
| public interface OpenInterfaceWitnessSuppression { |
| |
| boolean isSuppressed( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| TypeElement valueType, |
| DexClass openInterface); |
| } |
| |
| // Allow open interfaces by default. This is set to false in testing. |
| private boolean allowOpenInterfaces = true; |
| |
| // When open interfaces are not allowed, compilation fails with an assertion error unless each |
| // open interface witness is expected according to some suppression. |
| private List<OpenInterfaceWitnessSuppression> suppressions = new ArrayList<>(); |
| |
| public void disallowOpenInterfaces() { |
| allowOpenInterfaces = false; |
| } |
| |
| public OpenClosedInterfacesOptions suppressSingleOpenInterface(ClassReference classReference) { |
| assert !allowOpenInterfaces; |
| suppressions.add( |
| (appView, valueType, openInterface) -> |
| openInterface.getTypeName().equals(classReference.getTypeName())); |
| return this; |
| } |
| |
| public void suppressAllOpenInterfaces() { |
| assert !allowOpenInterfaces; |
| suppressions.add((appView, valueType, openInterface) -> true); |
| } |
| |
| public void suppressAllOpenInterfacesDueToMissingClasses() { |
| assert !allowOpenInterfaces; |
| suppressions.add( |
| (appView, valueType, openInterface) -> valueType.isBasedOnMissingClass(appView)); |
| } |
| |
| public void suppressArrayAssignmentsToJavaLangSerializable() { |
| assert !allowOpenInterfaces; |
| suppressions.add( |
| (appView, valueType, openInterface) -> |
| valueType.isArrayType() |
| && openInterface.getTypeName().equals("java.io.Serializable")); |
| } |
| |
| public void suppressZipFileAssignmentsToJavaLangAutoCloseable() { |
| assert !allowOpenInterfaces; |
| suppressions.add( |
| (appView, valueType, openInterface) -> |
| valueType.isClassType() |
| && valueType |
| .asClassType() |
| .getClassType() |
| .getTypeName() |
| .equals("java.util.zip.ZipFile") |
| && openInterface.getTypeName().equals("java.lang.AutoCloseable")); |
| } |
| |
| public boolean isOpenInterfacesAllowed() { |
| return allowOpenInterfaces; |
| } |
| |
| public boolean hasSuppressions() { |
| return !suppressions.isEmpty(); |
| } |
| |
| public boolean isSuppressed( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| TypeElement valueType, |
| DexClass openInterface) { |
| return allowOpenInterfaces |
| || suppressions.stream() |
| .anyMatch(suppression -> suppression.isSuppressed(appView, valueType, openInterface)); |
| } |
| } |
| |
| public static class MappingComposeOptions { |
| // TODO(b/241763080): Remove when enabled. |
| public boolean enableExperimentalMappingComposition = false; |
| // TODO(b/247136434): Disable for internal builds. |
| public boolean allowNonExistingOriginalRanges = true; |
| public Consumer<ClassNameMapper> generatedClassNameMapperConsumer = null; |
| } |
| |
| public static class ApiModelTestingOptions { |
| |
| // Flag to specify if we should load the database or not. The api database is used for |
| // library member rebinding. |
| public boolean enableLibraryApiModeling = |
| System.getProperty("com.android.tools.r8.disableApiModeling") == null; |
| |
| // The flag enableApiCallerIdentification controls if we can inline or merge targets with |
| // different api levels. It is also the flag that specifies if we assign api levels to |
| // references. |
| public boolean enableApiCallerIdentification = |
| System.getProperty("com.android.tools.r8.disableApiModeling") == null; |
| public boolean checkAllApiReferencesAreSet = |
| System.getProperty("com.android.tools.r8.disableApiModeling") == null; |
| public boolean enableStubbingOfClasses = |
| System.getProperty("com.android.tools.r8.disableApiModeling") == null; |
| public boolean enableOutliningOfMethods = |
| System.getProperty("com.android.tools.r8.disableApiModeling") == null; |
| public boolean reportUnknownApiReferences = |
| System.getProperty("com.android.tools.r8.reportUnknownApiReferences") != null; |
| |
| // TODO(b/232823652): Enable when we can compute the offset correctly. |
| public boolean useMemoryMappedByteBuffer = false; |
| |
| // A mapping from references to the api-level introducing them. |
| public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>(); |
| public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>(); |
| public Map<ClassReference, AndroidApiLevel> classApiMapping = new HashMap<>(); |
| public BiConsumer<MethodReference, ComputedApiLevel> tracedMethodApiLevelCallback = null; |
| |
| public void visitMockedApiLevelsForReferences( |
| DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) { |
| if (methodApiMapping.isEmpty() && fieldApiMapping.isEmpty() && classApiMapping.isEmpty()) { |
| return; |
| } |
| classApiMapping.forEach( |
| (classReference, apiLevel) -> { |
| apiLevelConsumer.accept(factory.createType(classReference.getDescriptor()), apiLevel); |
| }); |
| fieldApiMapping.forEach( |
| (fieldReference, apiLevel) -> { |
| apiLevelConsumer.accept(factory.createField(fieldReference), apiLevel); |
| }); |
| methodApiMapping.forEach( |
| (methodReference, apiLevel) -> { |
| apiLevelConsumer.accept(factory.createMethod(methodReference), apiLevel); |
| }); |
| } |
| |
| public boolean isApiLibraryModelingEnabled() { |
| return enableLibraryApiModeling; |
| } |
| |
| public boolean isCheckAllApiReferencesAreSet() { |
| return enableLibraryApiModeling && checkAllApiReferencesAreSet; |
| } |
| |
| public boolean isApiCallerIdentificationEnabled() { |
| return enableLibraryApiModeling && enableApiCallerIdentification; |
| } |
| |
| public void disableApiModeling() { |
| enableLibraryApiModeling = false; |
| enableApiCallerIdentification = false; |
| enableOutliningOfMethods = false; |
| enableStubbingOfClasses = false; |
| checkAllApiReferencesAreSet = false; |
| } |
| |
| /** |
| * Disable the workarounds for missing APIs. This does not disable the use of the database, just |
| * the introduction of soft-verification workarounds for potentially missing API references. |
| */ |
| public void disableOutliningAndStubbing() { |
| enableOutliningOfMethods = false; |
| enableStubbingOfClasses = false; |
| } |
| |
| public void disableApiCallerIdentification() { |
| enableApiCallerIdentification = false; |
| } |
| |
| public void disableStubbingOfClasses() { |
| enableStubbingOfClasses = false; |
| } |
| } |
| |
| public static class ProtoShrinkingOptions { |
| |
| public boolean enableGeneratedExtensionRegistryShrinking = false; |
| public boolean enableGeneratedMessageLiteShrinking = false; |
| public boolean enableGeneratedMessageLiteBuilderShrinking = false; |
| public boolean traverseOneOfAndRepeatedProtoFields = false; |
| public boolean enableEnumLiteProtoShrinking = false; |
| // Breaks the Chrome build if this is not enabled because of MethodToInvoke switchMaps. |
| // See b/174530756 for more details. |
| public boolean enableProtoEnumSwitchMapShrinking = true; |
| |
| public void disable() { |
| enableGeneratedExtensionRegistryShrinking = false; |
| enableGeneratedMessageLiteShrinking = false; |
| enableGeneratedMessageLiteBuilderShrinking = false; |
| traverseOneOfAndRepeatedProtoFields = false; |
| enableEnumLiteProtoShrinking = false; |
| } |
| |
| public boolean enableRemoveProtoEnumSwitchMap() { |
| return isProtoShrinkingEnabled() && enableProtoEnumSwitchMapShrinking; |
| } |
| |
| public boolean isProtoShrinkingEnabled() { |
| return enableGeneratedExtensionRegistryShrinking |
| || enableGeneratedMessageLiteShrinking |
| || enableGeneratedMessageLiteBuilderShrinking |
| || enableEnumLiteProtoShrinking; |
| } |
| |
| public boolean isEnumLiteProtoShrinkingEnabled() { |
| return enableEnumLiteProtoShrinking; |
| } |
| |
| public boolean shouldApplyInliningToInlinee( |
| AppView<?> appView, ProgramMethod inlinee, int inliningDepth) { |
| if (isProtoShrinkingEnabled() && inliningDepth == 1) { |
| ProtoReferences protoReferences = appView.protoShrinker().getProtoReferences(); |
| return inlinee.getHolderType() == protoReferences.generatedMessageLiteType; |
| } |
| return false; |
| } |
| } |
| |
| public static class TestingOptions { |
| |
| public boolean neverReuseCfLocalRegisters = false; |
| public boolean roundtripThroughLIR = false; |
| private boolean hasReadCheckDeterminism = false; |
| private DeterminismChecker determinismChecker = null; |
| public boolean usePcEncodingInCfForTesting = false; |
| public boolean dexContainerExperiment = |
| System.getProperty("com.android.tools.r8.dexContainerExperiment") != null; |
| |
| // Testing options to analyse locality of items in DEX files when they are generated. |
| public boolean calculateItemUseCountInDex = false; |
| public boolean calculateItemUseCountInDexDumpSingleUseStrings = false; |
| |
| private DeterminismChecker getDeterminismChecker() { |
| // Lazily read the env-var so that it can be set after options init. |
| if (determinismChecker == null && !hasReadCheckDeterminism) { |
| hasReadCheckDeterminism = true; |
| String dir = System.getProperty("com.android.tools.r8.checkdeterminism"); |
| if (dir != null) { |
| setDeterminismChecker(DeterminismChecker.createWithFileBacking(Paths.get(dir))); |
| } |
| } |
| return determinismChecker; |
| } |
| |
| public void setDeterminismChecker(DeterminismChecker checker) { |
| determinismChecker = checker; |
| } |
| |
| public void checkDeterminism(AppView<?> appView) { |
| DeterminismChecker determinismChecker = getDeterminismChecker(); |
| if (determinismChecker != null) { |
| determinismChecker.check(appView); |
| } |
| } |
| |
| public <E extends Exception> void checkDeterminism( |
| ThrowingConsumer<DeterminismChecker, E> consumer) { |
| DeterminismChecker determinismChecker = getDeterminismChecker(); |
| if (determinismChecker != null) { |
| consumer.acceptWithRuntimeException(determinismChecker); |
| } |
| } |
| |
| public static int NO_LIMIT = -1; |
| |
| public ArgumentPropagatorEventConsumer argumentPropagatorEventConsumer = |
| ArgumentPropagatorEventConsumer.emptyConsumer(); |
| |
| public Predicate<DexProgramClass> isEligibleForBridgeHoisting = Predicates.alwaysTrue(); |
| |
| // Force writing the specified bytes as the DEX version content. |
| public byte[] forceDexVersionBytes = null; |
| |
| public IROrdering irOrdering = |
| InternalOptions.assertionsEnabled() && !InternalOptions.DETERMINISTIC_DEBUGGING |
| ? NondeterministicIROrdering.getInstance() |
| : IdentityIROrdering.getInstance(); |
| |
| public BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy> |
| mixedSectionLayoutStrategyInspector = (strategy, virtualFile) -> strategy; |
| |
| public void setMixedSectionLayoutStrategyInspector( |
| BiFunction<MixedSectionLayoutStrategy, VirtualFile, MixedSectionLayoutStrategy> |
| mixedSectionLayoutStrategyInspector) { |
| this.mixedSectionLayoutStrategyInspector = mixedSectionLayoutStrategyInspector; |
| } |
| |
| public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null; |
| |
| public Consumer<String> processingContextsConsumer = null; |
| |
| public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration> |
| repackagingConfigurationFactory = DefaultRepackagingConfiguration::new; |
| |
| public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer = |
| ConsumerUtils.emptyBiConsumer(); |
| public Function<List<Policy>, List<Policy>> horizontalClassMergingPolicyRewriter = |
| Function.identity(); |
| public TriFunction<AppView<?>, Iterable<DexProgramClass>, DexProgramClass, DexProgramClass> |
| horizontalClassMergingTarget = (appView, candidates, target) -> target; |
| |
| public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer = |
| ConsumerUtils.emptyBiConsumer(); |
| |
| public BiConsumer<DexItemFactory, VerticallyMergedClasses> verticallyMergedClassesConsumer = |
| ConsumerUtils.emptyBiConsumer(); |
| |
| public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {}; |
| |
| public Consumer<DebugRepresentation> debugRepresentationCallback = null; |
| |
| /** |
| * If this flag is enabled, we will also compute the set of possible targets for invoke- |
| * interface and invoke-virtual instructions that target a library method, and add the |
| * corresponding edges to the call graph. |
| * |
| * <p>Setting this flag leads to more call graph edges, which can be good for size (e.g., it |
| * increases the likelihood that virtual methods have been processed by the time their call |
| * sites are processed, which allows more inlining). |
| * |
| * <p>However, the set of possible targets for such invokes can be very large. As an example, |
| * consider the instruction {@code invoke-virtual {v0, v1}, `void Object.equals(Object)`}). |
| * Therefore, tracing such invokes comes at a considerable performance penalty. |
| */ |
| public boolean addCallEdgesForLibraryInvokes = false; |
| |
| public boolean allowCheckDiscardedErrors = false; |
| public boolean allowClassInliningOfSynthetics = true; |
| public boolean allowInjectedAnnotationMethods = false; |
| public boolean allowInliningOfSynthetics = true; |
| public boolean allowTypeErrors = |
| !Version.isDevelopmentVersion() |
| || System.getProperty("com.android.tools.r8.allowTypeErrors") != null; |
| public boolean allowInvokeErrors = false; |
| public boolean allowUnnecessaryDontWarnWildcards = true; |
| public boolean allowUnusedDontWarnRules = true; |
| public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true; |
| public boolean alwaysUsePessimisticRegisterAllocation = false; |
| public boolean enableCheckCastAndInstanceOfRemoval = true; |
| public boolean enableDeadSwitchCaseElimination = true; |
| public boolean enableInvokeSuperToInvokeVirtualRewriting = true; |
| public boolean enableMultiANewArrayDesugaringForClassFiles = false; |
| public boolean enableRetargetingConstructorBridgeCalls = false; |
| public boolean enableSwitchToIfRewriting = true; |
| public boolean enableEnumUnboxingDebugLogs = |
| System.getProperty("com.android.tools.r8.enableEnumUnboxingDebugLogs") != null; |
| public boolean forceRedundantConstNumberRemoval = false; |
| public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false; |
| public boolean invertConditionals = false; |
| public boolean placeExceptionalBlocksLast = false; |
| public boolean forceJumboStringProcessing = false; |
| public boolean forcePcBasedEncoding = false; |
| public int pcBasedDebugEncodingOverheadThreshold = |
| System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold") != null |
| ? Integer.parseInt(System.getProperty("com.android.tools.r8.pc2pcOverheadThreshold")) |
| : 200000; |
| public Set<Inliner.Reason> validInliningReasons = null; |
| public boolean noLocalsTableOnInput = false; |
| public boolean forceNameReflectionOptimization = false; |
| public boolean enableNarrowAndWideningingChecksInD8 = false; |
| public BiConsumer<IRCode, AppView<?>> irModifier = null; |
| public Consumer<IRCode> inlineeIrModifier = null; |
| public int basicBlockMuncherIterationLimit = NO_LIMIT; |
| public boolean dontReportFailingCheckDiscarded = false; |
| public boolean disableRecordApplicationReaderMap = false; |
| public PrintStream whyAreYouNotInliningConsumer = System.out; |
| public boolean trackDesugaredAPIConversions = |
| System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null; |
| public boolean enumUnboxingRewriteJavaCGeneratedMethod = false; |
| // TODO(b/154793333): Enable assertions always when resolved. |
| public boolean assertConsistentRenamingOfSignature = false; |
| public boolean allowStaticInterfaceMethodsForPreNApiLevel = false; |
| public int verificationSizeLimitInBytesOverride = -1; |
| public boolean forceIRForCfToCfDesugar = |
| System.getProperty("com.android.tools.r8.forceIRForCfToCfDesugar") != null; |
| public boolean disableMappingToOriginalProgramVerification = false; |
| public boolean allowInvalidCfAccessFlags = |
| System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null; |
| public boolean verifyInputs = System.getProperty("com.android.tools.r8.verifyInputs") != null; |
| // TODO(b/177333791): Set to true |
| public boolean checkForNotExpandingMainDexTracingResult = false; |
| public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>(); |
| public boolean enableTestAssertions = |
| System.getProperty("com.android.tools.r8.enableTestAssertions") != null; |
| public boolean disableMarkingMethodsFinal = |
| System.getProperty("com.android.tools.r8.disableMarkingMethodsFinal") != null; |
| public boolean disableMarkingClassesFinal = |
| System.getProperty("com.android.tools.r8.disableMarkingClassesFinal") != null; |
| public boolean testEnableTestAssertions = false; |
| public boolean keepMetadataInR8IfNotRewritten = true; |
| |
| // If set, pruned record fields are not used in hashCode/equals/toString and toString prints |
| // minified field names instead of original field names. |
| public boolean enableRecordModeling = true; |
| |
| // Flag to allow processing of resources in D8. A data resource consumer still needs to be |
| // specified. |
| public boolean enableD8ResourcesPassThrough = false; |
| |
| // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes. |
| public boolean allowNonAbstractClassesWithAbstractMethods = true; |
| |
| public boolean verifyKeptGraphInfo = false; |
| |
| public boolean readInputStackMaps = true; |
| public boolean disableStackMapVerification = false; |
| |
| public boolean disableShortenLiveRanges = false; |
| |
| // Option for testing outlining with interface array arguments, see b/132420510. |
| public boolean allowOutlinerInterfaceArrayArguments = false; |
| |
| public int limitNumberOfClassesPerDex = -1; |
| |
| public MinifierTestingOptions minifier = new MinifierTestingOptions(); |
| |
| // Testing hooks to trigger effects in various compiler places. |
| public Runnable hookInIrConversion = null; |
| |
| public static class MinifierTestingOptions { |
| |
| public Comparator<DexMethod> interfaceMethodOrdering = null; |
| |
| public Comparator<Wrapper<DexEncodedMethod>> getInterfaceMethodOrderingOrDefault( |
| Comparator<Wrapper<DexEncodedMethod>> comparator) { |
| if (interfaceMethodOrdering != null) { |
| return (a, b) -> |
| interfaceMethodOrdering.compare(a.get().getReference(), b.get().getReference()); |
| } |
| return comparator; |
| } |
| } |
| |
| public boolean measureProguardIfRuleEvaluations = false; |
| public ProguardIfRuleEvaluationData proguardIfRuleEvaluationData = |
| new ProguardIfRuleEvaluationData(); |
| |
| public static class ProguardIfRuleEvaluationData { |
| |
| public int numberOfProguardIfRuleClassEvaluations = 0; |
| public int numberOfProguardIfRuleMemberEvaluations = 0; |
| } |
| |
| public Consumer<ProgramMethod> callSiteOptimizationInfoInspector = |
| ConsumerUtils.emptyConsumer(); |
| |
| public Predicate<DexMethod> cfByteCodePassThrough = null; |
| |
| public boolean enableExperimentalMapFileVersion = false; |
| |
| public boolean alwaysGenerateLambdaFactoryMethods = false; |
| } |
| |
| public MapVersion getMapFileVersion() { |
| return testing.enableExperimentalMapFileVersion |
| ? MapVersion.MAP_VERSION_EXPERIMENTAL |
| : MapVersion.STABLE; |
| } |
| |
| @VisibleForTesting |
| public void disableNameReflectionOptimization() { |
| // Use this util to disable get*Name() computation if the main intention of tests is checking |
| // const-class, e.g., canonicalization, or some test classes' only usages are get*Name(). |
| enableNameReflectionOptimization = false; |
| } |
| |
| private boolean hasMinApi(AndroidApiLevel level) { |
| return getMinApiLevel().isGreaterThanOrEqualTo(level); |
| } |
| |
| /** |
| * Predicate to guard on the support of a language feature. |
| * |
| * <p>Note that if not desugaring or compiling to DEX, then the output is a mapping of the input |
| * and thus all parts should be representable (assuming the compiler has support for them). |
| */ |
| private boolean hasFeaturePresentFrom(AndroidApiLevel level) { |
| if (desugarState.isOn() || isGeneratingDex()) { |
| return level != null && hasMinApi(level); |
| } |
| // If not desugaring and not compiling to DEX, then the API level is effectively ignored and |
| // we assume that everything in the input is supported in the output. |
| assert minApiLevel.equals(B); |
| return true; |
| } |
| |
| /** |
| * Predicate to guard against the possible presence of a VM bug. |
| * |
| * <p>Note that if the compilation is not desugaring to a min-api or targeting DEX at a min-api, |
| * then the bug is assumed to be present as the CF output could be futher compiled to any target. |
| */ |
| private boolean canHaveBugPresentUntil(AndroidApiLevel level) { |
| if (desugarState.isOn() || isGeneratingDex()) { |
| return level == null || !hasMinApi(level); |
| } |
| assert minApiLevel.equals(B); |
| return true; |
| } |
| |
| /** |
| * Allow access modification of synthetic lambda implementation methods in D8 to avoid generating |
| * an excessive amount of accessibility bridges. In R8, the lambda implementation methods are |
| * inlined into the synthesized accessibility bridges, thus we don't allow access modification. |
| */ |
| public boolean canAccessModifyLambdaImplementationMethods(AppView<?> appView) { |
| return !appView.enableWholeProgramOptimizations(); |
| } |
| |
| /** |
| * Dex2Oat issues a warning for abstract methods on non-abstract classes, so we never allow this. |
| * |
| * <p>Note that having an invoke instruction that targets an abstract method on a non-abstract |
| * class will fail with a verification error on Dalvik. Therefore, this must not be more |
| * permissive than {@code return minApiLevel >= AndroidApiLevel.L.getLevel()}. |
| * |
| * <p>See b/132953944. |
| */ |
| @SuppressWarnings("ConstantConditions") |
| public boolean canUseAbstractMethodOnNonAbstractClass() { |
| boolean result = false; |
| assert !(result && canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()); |
| return result; |
| } |
| |
| public boolean canUseConstClassInstructions(CfVersion cfVersion) { |
| assert isGeneratingClassFiles(); |
| return cfVersion.isGreaterThanOrEqualTo(requiredCfVersionForConstClassInstructions()); |
| } |
| |
| public CfVersion requiredCfVersionForConstClassInstructions() { |
| assert isGeneratingClassFiles(); |
| return CfVersion.V1_5; |
| } |
| |
| public static AndroidApiLevel invokePolymorphicOnMethodHandleApiLevel() { |
| return AndroidApiLevel.O; |
| } |
| |
| public boolean canUseInvokePolymorphicOnMethodHandle() { |
| return hasFeaturePresentFrom(invokePolymorphicOnMethodHandleApiLevel()); |
| } |
| |
| public static AndroidApiLevel invokePolymorphicOnVarHandleApiLevel() { |
| return AndroidApiLevel.P; |
| } |
| |
| public boolean canUseInvokePolymorphicOnVarHandle() { |
| return hasFeaturePresentFrom(invokePolymorphicOnMethodHandleApiLevel()); |
| } |
| |
| public static AndroidApiLevel constantMethodHandleApiLevel() { |
| return AndroidApiLevel.P; |
| } |
| |
| public boolean canUseConstantMethodHandle() { |
| return hasFeaturePresentFrom(constantMethodHandleApiLevel()); |
| } |
| |
| public static AndroidApiLevel constantMethodTypeApiLevel() { |
| return AndroidApiLevel.P; |
| } |
| |
| public boolean canUseConstantMethodType() { |
| return hasFeaturePresentFrom(constantMethodTypeApiLevel()); |
| } |
| |
| public static AndroidApiLevel invokeCustomApiLevel() { |
| return AndroidApiLevel.O; |
| } |
| |
| public boolean canUseInvokeCustom() { |
| return hasFeaturePresentFrom(invokeCustomApiLevel()); |
| } |
| |
| public static AndroidApiLevel constantDynamicApiLevel() { |
| return null; |
| } |
| |
| public boolean canUseConstantDynamic() { |
| return hasFeaturePresentFrom(constantDynamicApiLevel()); |
| } |
| |
| public static AndroidApiLevel defaultAndStaticInterfaceMethodsApiLevel() { |
| return AndroidApiLevel.N; |
| } |
| |
| public static AndroidApiLevel defaultInterfaceMethodsApiLevel() { |
| return defaultAndStaticInterfaceMethodsApiLevel(); |
| } |
| |
| public static AndroidApiLevel staticInterfaceMethodsApiLevel() { |
| return defaultAndStaticInterfaceMethodsApiLevel(); |
| } |
| |
| public boolean canUseDefaultAndStaticInterfaceMethods() { |
| return hasFeaturePresentFrom(defaultInterfaceMethodsApiLevel()); |
| } |
| |
| public static AndroidApiLevel privateInterfaceMethodsApiLevel() { |
| return AndroidApiLevel.N; |
| } |
| |
| public boolean canUsePrivateInterfaceMethods() { |
| return hasFeaturePresentFrom(privateInterfaceMethodsApiLevel()); |
| } |
| |
| public boolean canUseNestBasedAccess() { |
| return hasFeaturePresentFrom(null) || emitNestAnnotationsInDex; |
| } |
| |
| public boolean canUseRecords() { |
| return hasFeaturePresentFrom(null) || emitRecordAnnotationsInDex; |
| } |
| |
| public boolean canUseSealedClasses() { |
| return hasFeaturePresentFrom(null) || emitPermittedSubclassesAnnotationsInDex; |
| } |
| |
| public boolean canLeaveStaticInterfaceMethodInvokes() { |
| return hasFeaturePresentFrom(AndroidApiLevel.L); |
| } |
| |
| public boolean canUseTwrCloseResourceMethod() { |
| return hasFeaturePresentFrom(AndroidApiLevel.K); |
| } |
| |
| public boolean canUseSpacesInSimpleName() { |
| return itemFactory.getSkipNameValidationForTesting() |
| || hasFeaturePresentFrom(AndroidApiLevel.R); |
| } |
| |
| public boolean enableBackportedMethodRewriting() { |
| // Disable rewriting if there are no methods to rewrite or if the API level is higher than |
| // the highest known API level when the compiler is built. This ensures that when this is used |
| // by the Android Platform build (which normally use an API level of 10000) there will be |
| // no rewriting of backported methods. See b/147480264. |
| return enableBackportMethods |
| && desugarState.isOn() |
| // TODO(b/232073181): This platform check should rather be controlled via the platform flag. |
| && getMinApiLevel().isLessThanOrEqualTo(AndroidApiLevel.LATEST); |
| } |
| |
| public boolean enableTryWithResourcesDesugaring() { |
| switch (tryWithResourcesDesugaring) { |
| case Off: |
| return false; |
| case Auto: |
| return desugarState.isOn() && !canUseTwrCloseResourceMethod(); |
| } |
| throw new Unreachable(); |
| } |
| |
| // Debug entries may be dropped only if the source file content allows being omitted from |
| // stack traces, or if the VM will report the source file even with a null valued debug info. |
| public boolean allowDiscardingResidualDebugInfo() { |
| // TODO(b/146565491): We can drop debug info once fixed at a known min-api. |
| return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile(); |
| } |
| |
| public boolean canUseDexPc2PcAsDebugInformation() { |
| return isGeneratingDex() && lineNumberOptimization == LineNumberOptimization.ON; |
| } |
| |
| public boolean canUseNativeDexPcInsteadOfDebugInfo() { |
| return canUseDexPc2PcAsDebugInformation() |
| && hasMinApi(AndroidApiLevel.O) |
| && allowDiscardingResidualDebugInfo(); |
| } |
| |
| public boolean isInterfaceMethodDesugaringEnabled() { |
| // This condition is to filter out tests that never set program consumer. |
| if (!hasConsumer()) { |
| return false; |
| } |
| return desugarState.isOn() |
| && interfaceMethodDesugaring == OffOrAuto.Auto |
| && !canUseDefaultAndStaticInterfaceMethods(); |
| } |
| |
| public boolean isSwitchRewritingEnabled() { |
| return enableSwitchRewriting && !debug; |
| } |
| |
| public boolean isStringSwitchConversionEnabled() { |
| return enableStringSwitchConversion && !debug; |
| } |
| |
| public boolean canUseMultidex() { |
| assert isGeneratingDex(); |
| return intermediate || hasMinApi(AndroidApiLevel.L); |
| } |
| |
| public boolean canUseJavaUtilObjects() { |
| return hasFeaturePresentFrom(AndroidApiLevel.K); |
| } |
| |
| public boolean canUseJavaUtilObjectsIsNull() { |
| return hasFeaturePresentFrom(AndroidApiLevel.N); |
| } |
| |
| public boolean canUseSuppressedExceptions() { |
| // TODO(b/214239152): Suppressed exceptions are @hide from at least 4.0.1 / Android I / API 14. |
| return hasFeaturePresentFrom(AndroidApiLevel.K); |
| } |
| |
| public boolean canUseAssertionErrorTwoArgumentConstructor() { |
| return hasFeaturePresentFrom(AndroidApiLevel.K); |
| } |
| |
| public CfVersion classFileVersionAfterDesugaring(CfVersion version) { |
| assert isGeneratingClassFiles(); |
| if (!isDesugaring()) { |
| return version; |
| } |
| CfVersion maxVersionAfterDesugar = |
| canUseDefaultAndStaticInterfaceMethods() ? CfVersion.V1_8 : CfVersion.V1_7; |
| return Ordered.min(maxVersionAfterDesugar, version); |
| } |
| |
| // The Apache Harmony-based AssertionError constructor which takes an Object on API 15 and older |
| // calls the Error supertype constructor with null as the exception cause. This prevents |
| // subsequent calls to initCause() because its implementation checks that cause==this before |
| // allowing a cause to be set. |
| // |
| // https://android.googlesource.com/platform/libcore/+/refs/heads/ics-mr1/luni/src/main/java/java/lang/AssertionError.java#56 |
| public boolean canInitCauseAfterAssertionErrorObjectConstructor() { |
| return hasFeaturePresentFrom(AndroidApiLevel.J); |
| } |
| |
| // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for |
| // arrays of objects. This is unfortunate, since this never hits arm devices, but we have |
| // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was |
| // removed during the jelly-bean release cycle and is not there from kitkat. |
| // |
| // Buggy code that accidentally call code that only works on primitives arrays. |
| // |
| // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106 |
| public boolean canUseFilledNewArrayOfObjects() { |
| assert isGeneratingDex(); |
| return hasFeaturePresentFrom(AndroidApiLevel.K); |
| } |
| |
| // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter |
| // where an aget-wide instruction using the same register for the array |
| // and the first register of the result could lead to the wrong exception |
| // being thrown on out of bounds. |
| public boolean canUseSameArrayAndResultRegisterInArrayGetWide() { |
| return hasFeaturePresentFrom(AndroidApiLevel.P); |
| } |
| |
| // Some Lollipop versions of Art found in the wild perform invalid bounds |
| // check elimination. There is a fast path of loops and a slow path. |
| // The bailout to the slow path is performed too early and therefore |
| // the array-index variable might not be defined in the slow path code leading |
| // to use of undefined registers as indices into arrays. The result |
| // is ArrayIndexOutOfBounds exceptions. |
| // |
| // In an attempt to help these Art VMs, all single-width constants are initialized and not moved. |
| // |
| // There is no guarantee that this works, but it does make the problem |
| // disappear on the one known instance of this problem. |
| // |
| // See b/69364976 and b/77996377. |
| public boolean canHaveBoundsCheckEliminationBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // MediaTek JIT compilers for KitKat phones did not implement the not |
| // instruction as it was not generated by DX. Therefore, apps containing |
| // not instructions would crash if the code was JIT compiled. Therefore, |
| // we can only use not instructions if we are targeting Art-based |
| // phones. |
| public boolean canUseNotInstruction() { |
| return hasFeaturePresentFrom(AndroidApiLevel.L); |
| } |
| |
| // Art before M has a verifier bug where the type of the contents of the receiver register is |
| // assumed to not change. If the receiver register is reused for something else the verifier |
| // will fail and the code will not run. |
| public boolean canHaveThisTypeVerifierBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // Art crashes if we do dead reference elimination of the receiver in release mode and Art |
| // is asked for the |this| object over a JDWP connection at a point where the receiver |
| // register has been clobbered. |
| // |
| // See b/116683601 and b/116837585. |
| public boolean canHaveThisJitCodeDebuggingBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.Q); |
| } |
| |
| // The dalvik jit had a bug where the long operations add, sub, or, xor and and would write |
| // the first part of the result long before reading the second part of the input longs. |
| public boolean canHaveOverlappingLongRegisterBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // Some dalvik versions found in the wild perform invalid JIT compilation of cmp-long |
| // instructions where the result register overlaps with the input registers. |
| // See b/74084493. |
| // |
| // The same dalvik versions also have a bug where the JIT compilation of code such as: |
| // |
| // void method(long l) { |
| // if (l < 0) throw new RuntimeException("less than"); |
| // if (l == 0) throw new RuntimeException("equal"); |
| // } |
| // |
| // Will enter the case for l==0 even when l is non-zero. The code generated for this is of |
| // the form: |
| // |
| // 0: 0x00: ConstWide16 v0, 0x0000000000000000 (0) |
| // 1: 0x02: CmpLong v2, v4, v0 |
| // 2: 0x04: IfLtz v2, 0x0c (+8) |
| // 3: 0x06: IfNez v2, 0x0a (+4) |
| // |
| // However, the jit apparently clobbers the input register in the IfLtz instruction. Therefore, |
| // for dalvik VMs we have to instead generate the following code: |
| // |
| // 0: 0x00: ConstWide16 v0, 0x0000000000000000 (0) |
| // 1: 0x02: CmpLong v2, v4, v0 |
| // 2: 0x04: IfLtz v2, 0x0e (+10) |
| // 3: 0x06: CmpLong v2, v4, v0 |
| // 4: 0x08: IfNez v2, 0x0c (+4) |
| // |
| // See b/75408029. |
| public boolean canHaveCmpLongBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // Some Lollipop VMs crash if there is a const instruction between a cmp and an if instruction. |
| // |
| // Crashing code: |
| // |
| // :goto_0 |
| // cmpg-float v0, p0, p0 |
| // const/4 v1, 0 |
| // if-gez v0, :cond_0 |
| // add-float/2addr p0, v1 |
| // goto :goto_0 |
| // :cond_0 |
| // return p0 |
| // |
| // Working code: |
| // :goto_0 |
| // const/4 v1, 0 |
| // cmpg-float v0, p0, p0 |
| // if-gez v0, :cond_0 |
| // add-float/2addr p0, v1 |
| // goto :goto_0 |
| // :cond_0 |
| // return p0 |
| // |
| // See b/115552239. |
| public boolean canHaveCmpIfFloatBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // Some Lollipop VMs incorrectly optimize code with mul2addr instructions. In particular, |
| // the following hash code method produces wrong results after optimizations: |
| // |
| // 0: 0x00: IgetObject v0, v3, Field java.lang.Class MultiClassKey.first |
| // 1: 0x02: InvokeVirtual { v0 } Ljava/lang/Object;->hashCode()I |
| // 2: 0x05: MoveResult v0 |
| // 3: 0x06: Const16 v1, 0x001f (31) |
| // 4: 0x08: MulInt2Addr v1, v0 |
| // 5: 0x09: IgetObject v2, v3, Field java.lang.Class MultiClassKey.second |
| // 6: 0x0b: InvokeVirtual { v2 } Ljava/lang/Object;->hashCode()I |
| // 7: 0x0e: MoveResult v2 |
| // 8: 0x0f: AddInt2Addr v1, v2 |
| // 9: 0x10: Return v1 |
| // |
| // It seems that the issue is the MulInt2Addr instructions. Avoiding that, the VM computes |
| // hash codes correctly also after optimizations. |
| // |
| // This issue has only been observed on a Verizon Ellipsis 8 tablet. See b/76115465. |
| public boolean canHaveMul2AddrBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // Some Marshmallow VMs create an incorrect doubly-linked list of instructions. When the VM |
| // attempts to create a fixup for a Cortex 53 long add/sub issue, it may diverge due to the cyclic |
| // list. |
| // |
| // See b/77842465. |
| public boolean canHaveDex2OatLinkedListBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.N); |
| } |
| |
| // dex2oat on Marshmallow VMs does aggressive inlining which can eat up all the memory on |
| // devices for self-recursive methods. |
| // |
| // See b/111960171 |
| public boolean canHaveDex2OatInliningIssue() { |
| return canHaveBugPresentUntil(AndroidApiLevel.N); |
| } |
| |
| // Art 7.0.0 and later Art JIT may perform an invalid optimization if a string new-instance does |
| // not flow directly to the init call. |
| // |
| // See b/78493232 and b/80118070. |
| public boolean canHaveArtStringNewInitBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.Q); |
| } |
| |
| // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to |
| // double and used in arithmetic operations. |
| // |
| // See b/77496850. |
| public boolean canHaveNumberConversionRegisterAllocationBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // Some Lollipop mediatek VMs have a peculiar bug where the inliner crashes if there is a |
| // simple constructor that just forwards its arguments to the super constructor. Strangely, |
| // this happens only for specific signatures: so far the only reproduction we have is for |
| // a constructor accepting two doubles and one object. |
| // |
| // To workaround this we insert a materializing const instruction before the super init |
| // call. Having a temporary register seems to disable the buggy optimizations. |
| // |
| // See b/68378480. |
| public boolean canHaveForwardingInitInliningBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // Some Lollipop x86_64 VMs have a bug causing a segfault if an exception handler directly targets |
| // a conditional-loop header. This cannot happen for debug builds as the existence of a |
| // move-exception instruction will ensure a non-direct target. |
| // |
| // To workaround this in release builds, we insert a materializing nop instruction in the |
| // exception handler forcing it not directly target any loop header. |
| // |
| // See b/111337896. |
| public boolean canHaveExceptionTargetingLoopHeaderBug() { |
| assert isGeneratingDex(); |
| return !debug && canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // The Dalvik tracing JIT can trace past the end of the instruction stream and end up |
| // parsing non-code bytes as code (typically leading to a crash). See b/117907456. |
| // |
| // In order to workaround this we insert a goto past the throw, and another goto after the throw |
| // jumping back to the throw. |
| |
| // We used to insert a empty loop at the end, however, mediatek has an optimizer |
| // on lollipop devices that cannot deal with an unreachable infinite loop, so we |
| // couldn't do that. See b/119895393. |
| // We also could not insert any dead code (e.g. a return) because that would make mediatek |
| // dominator calculations on 7.0.0 crash. See b/128926846. |
| public boolean canHaveTracingPastInstructionsStreamBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // The art verifier incorrectly propagates type information for the following pattern: |
| // |
| // move vA, vB |
| // instance-of vB, vA, Type |
| // if-eqz/nez vB |
| // |
| // In that case it will assume that vB has object type after the if. Therefore, if the |
| // result of the instance-of operation is reused in a boolean context the verifier will |
| // fail with a type conflict. |
| // |
| // In order to make sure that cannot happen, we insert a nop between the move and |
| // the instance-of instruction so that this pattern in the art verifier does not |
| // match. |
| // |
| // move vA, vB |
| // nop |
| // instance-of vB, vA, Type |
| // if-eqz/nez vB |
| // |
| // This happens rarely, but it can happen in debug mode where the move |
| // put a value into a new register which has associated locals information. |
| // |
| // Fixed in Android Q, see b/120985556. |
| public boolean canHaveArtInstanceOfVerifierBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.Q); |
| } |
| |
| // Some Art Lollipop version do not deal correctly with long-to-int conversions. |
| // |
| // In particular, the following code performs an out of bounds array access when the |
| // long loaded from the long array is very large (has non-zero values in the upper 32 bits). |
| // |
| // aget-wide v9, v3, v1 |
| // long-to-int v9, v9 |
| // aget-wide v10, v3, v9 |
| // |
| // The issue seems to be that the higher bits of the 64-bit register holding the long |
| // are not cleared and the integer is therefore a 64-bit integer that is not truncated |
| // to 32 bits. |
| // |
| // As a workaround, we do not allow long-to-int to have the same source and target register |
| // for min-apis where lollipop devices could be targeted. |
| // |
| // See b/80262475. |
| public boolean canHaveLongToIntBug() { |
| // We have only seen this happening on Lollipop arm64 backends. We have tested on |
| // Marshmallow and Nougat arm64 devices and they do not have the bug. |
| return canHaveBugPresentUntil(AndroidApiLevel.M); |
| } |
| |
| // The Art VM for Android N through P has a bug in the JIT that means that if the same |
| // exception block with a move-exception instruction is targeted with more than one type |
| // of exception the JIT will incorrectly assume that the exception object has one of these |
| // types and will optimize based on that one type instead of taking all the types into account. |
| // |
| // In order to workaround that, we always generate distinct move-exception instructions for |
| // distinct dex types. |
| // |
| // See b/120164595. |
| public boolean canHaveExceptionTypeBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.Q); |
| } |
| |
| // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an |
| // aget instruction. We therefore need to be careful when performing trivial check-cast |
| // elimination of check-cast instructions where the value being cast is the constant null. |
| // See b/123269162. |
| public boolean canHaveArtCheckCastVerifierBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.J); |
| } |
| |
| // The verifier will merge A[] and B[] to Object[], even when both A and B implement an interface |
| // I, i.e., the join should have been I[]. This can lead to verification errors when the value is |
| // used as an I[]. |
| // |
| // See b/69826014. |
| public boolean canHaveIncorrectJoinForArrayOfInterfacesBug() { |
| return true; |
| } |
| |
| // The dalvik verifier will crash the program if there is a try catch block with an exception |
| // type that does not exist. |
| // We don't do anything special about this, except that we don't inline methods that have a |
| // catch handler with the ReflectiveOperationException type, i.e., if the program did not crash |
| // in the non R8 case it should not in the R8 case. |
| // Currently we handle only the ReflectiveOperationException, but there could be other exceptions. |
| // We do this for all pre art version, in case we add more Exception types later on. The |
| // problem is there for all dalvik vms, but the exception was added in api level 19 |
| // so we don't see it there. |
| // |
| // See b/131349148 |
| public boolean canHaveDalvikCatchHandlerVerificationBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // Having an invoke instruction that targets an abstract method on a non-abstract class will fail |
| // with a verification error. |
| // |
| // See b/132953944. |
| public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // On dalvik we see issues when using an int value in places where a boolean, byte, char, or short |
| // is expected. |
| // |
| // For example, if we inline the following method into the call site: |
| // public int value; |
| // public boolean getValue() { |
| // return value; |
| // } |
| // |
| // See also b/134304597 and b/124152497. |
| public boolean canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.L); |
| } |
| |
| // The standard library prior to API 19 did not contain a ZipFile that implemented Closable. |
| // |
| // See b/177532008. |
| public boolean canHaveZipFileWithMissingCloseableBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.K); |
| } |
| |
| // Some versions of Dalvik had a bug where a switch with a MAX_INT key would still go to |
| // the default case when switching on the value MAX_INT. |
| // |
| // See b/177790310. |
| public boolean canHaveSwitchMaxIntBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.K); |
| } |
| |
| // On Dalvik the methods Integer.parseInt and Long.parseLong does not support strings with a '+' |
| // prefix |
| // |
| // See b/182137865. |
| public boolean canParseNumbersWithPlusPrefix() { |
| return hasFeaturePresentFrom(AndroidApiLevel.L); |
| } |
| |
| // Lollipop and Marshmallow devices do not correctly handle invoke-super when the static holder |
| // is higher up in the hierarchy than the method that the invoke-super should resolve to. |
| // |
| // See b/215573892. |
| public boolean canHaveSuperInvokeBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.N); |
| } |
| |
| // Some Dalvik and Art MVs does not support interface invokes to Object |
| // members not explicitly defined on the symbolic reference of the |
| // interface invoke. In these cases rewrite to a virtual invoke with |
| // the symbolic reference java.lang.Object. |
| // |
| // The support was added in Android O, however at least for j.l.CharSequence.equals the handling |
| // in Art was incorrect (b/231450655). |
| // |
| // javac started generating code like this with the fix for JDK-8272564, which will be part of |
| // JDK 18. |
| // |
| // See b/218298666. |
| public boolean canHaveInvokeInterfaceToObjectMethodBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.P); |
| } |
| |
| // Until we fully drop support for API levels < 16, we have to emit an empty annotation set to |
| // work around a DALVIK bug. See b/36951668. |
| public boolean canHaveDalvikEmptyAnnotationSetBug() { |
| return canHaveBugPresentUntil(AndroidApiLevel.J_MR1); |
| } |
| |
| public boolean canHaveNonReboundConstructorInvoke() { |
| // TODO(b/246679983): Turned off while diagnosing b/246679983. |
| return false && isGeneratingDex() && minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L); |
| } |
| } |