Merge commit '4732fa63e34129422d08843061e8597b19c691cd' into dev-release
diff --git a/foo.txt b/foo.txt
new file mode 100644
index 0000000..d896812
--- /dev/null
+++ b/foo.txt
@@ -0,0 +1,25 @@
+Caused by: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.AssertionError
+ at com.android.tools.r8.dex.a.a(SourceFile:298)
+ at com.android.tools.r8.dex.a.a(SourceFile:227)
+ at com.android.tools.r8.dex.a.a(SourceFile:217)
+ at com.android.tools.r8.dex.a.a(SourceFile:214)
+ at com.android.tools.r8.ResourceShrinker.run(ResourceShrinker.java:5)
+ at com.android.builder.dexing.R8ResourceShrinker.runResourceShrinkerAnalysis(r8ResourceShrinker.kt:33)
+ at com.android.build.gradle.tasks.ResourceUsageAnalyzer.recordClassUsages(ResourceUsageAnalyzer.java:1493)
+ at com.android.build.gradle.tasks.ResourceUsageAnalyzer.recordClassUsages(ResourceUsageAnalyzer.java:1412)
+ at com.android.build.gradle.tasks.ResourceUsageAnalyzer.recordClassUsages(ResourceUsageAnalyzer.java:1406)
+ at com.android.build.gradle.tasks.ResourceUsageAnalyzer.analyze(ResourceUsageAnalyzer.java:288)
+ at com.android.build.gradle.internal.transforms.ShrinkResourcesTransform.splitAction(ShrinkResourcesTransform.java:317)
+ at com.android.build.gradle.internal.transforms.ShrinkResourcesTransform.lambda$transform$0(ShrinkResourcesTransform.java:248)
+ at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$$inlined$forEach$lambda$1.call(BuildElements.kt:121)
+ at com.android.build.gradle.internal.scope.BuildElements$ExecutorBasedScheduler$transform$$inlined$forEach$lambda$1.call(BuildElements.kt:110)
+Caused by: java.util.concurrent.ExecutionException: java.lang.AssertionError
+ at com.android.tools.r8.utils.Y0.a(SourceFile:50)
+ at com.android.tools.r8.dex.a.a(SourceFile:278)
+ ... 13 more
+Caused by: java.lang.AssertionError
+ at com.android.tools.r8.graph.W$b.a(SourceFile:3)
+ at com.android.tools.r8.graph.W.o0(SourceFile:6)
+ at com.android.tools.r8.dex.n.a(SourceFile:707)
+ at com.android.tools.r8.dex.n.a(SourceFile:989)
+ at com.android.tools.r8.dex.a$a.a(SourceFile:90)
diff --git a/src/main/java/com/android/tools/r8/CompilationFailedException.java b/src/main/java/com/android/tools/r8/CompilationFailedException.java
index dc9fa64..0078c06 100644
--- a/src/main/java/com/android/tools/r8/CompilationFailedException.java
+++ b/src/main/java/com/android/tools/r8/CompilationFailedException.java
@@ -15,7 +15,11 @@
}
public CompilationFailedException(Throwable cause) {
- super("Compilation failed to complete", cause);
+ this("Compilation failed to complete", cause);
+ }
+
+ public CompilationFailedException(String message, Throwable cause) {
+ super(message, cause);
}
public CompilationFailedException(String message) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 382dd08..d3a291f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -47,9 +47,11 @@
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.kotlin.KotlinMetadataUtils;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.Minifier;
import com.android.tools.r8.naming.NamingLens;
@@ -274,6 +276,10 @@
AppView.createForR8(new AppInfoWithClassHierarchy(application), options);
appView.setAppServices(AppServices.builder(appView).build());
+ // Check for potentially having pass-through of Cf-code for kotlin libraries.
+ options.enableCfByteCodePassThrough =
+ options.isGeneratingClassFiles() && KotlinMetadataUtils.mayProcessKotlinMetadata(appView);
+
// Up-front check for valid library setup.
if (!options.mainDexKeepRules.isEmpty()) {
MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
@@ -284,7 +290,13 @@
InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
BackportedMethodRewriter.registerAssumedLibraryTypes(options);
if (options.enableEnumUnboxing) {
- EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+ if (application.definitionFor(options.itemFactory.enumUnboxingUtilityType) != null) {
+ // The enum unboxing utility class can be created only during cf to dex compilation.
+ // If this is true, we are recompiling the dex application with R8 (compilation-steps).
+ options.enableEnumUnboxing = false;
+ } else {
+ EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+ }
}
List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
@@ -369,6 +381,15 @@
TreePruner pruner = new TreePruner(appViewWithLiveness);
application = pruner.run(application);
+ if (options.enableEnumUnboxing) {
+ DexProgramClass utilityClass =
+ EnumUnboxingRewriter.synthesizeEmptyEnumUnboxingUtilityClass(appView);
+ // We cannot know at this point if the class will be on the main dex list,
+ // updated later. Since this is inserted in the app at this point, we do not need
+ // to use any synthesized class hack and add the class as a program class.
+ application = application.builder().addProgramClass(utilityClass).build();
+ }
+
// Recompute the subtyping information.
Set<DexType> removedClasses = pruner.getRemovedClasses();
appView.setAppInfo(
@@ -465,7 +486,11 @@
timing.end();
}
- if (options.getProguardConfiguration().isOptimizing()) {
+ boolean isKotlinLibraryCompilationWithInlinePassThrough =
+ options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
+
+ if (!isKotlinLibraryCompilationWithInlinePassThrough
+ && options.getProguardConfiguration().isOptimizing()) {
if (options.enableHorizontalClassMerging) {
timing.begin("HorizontalStaticClassMerger");
StaticClassMerger staticClassMerger =
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 895c680..656f3aa 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -858,6 +858,11 @@
internal.outline.enabled = false;
internal.enableEnumUnboxing = false;
}
+ if (!internal.isShrinking()) {
+ // If R8 is not shrinking, there is no point in unboxing enums since the unboxed enums
+ // will still remain in the program (The application size would actually increase).
+ internal.enableEnumUnboxing = false;
+ }
// Amend the proguard-map consumer with options from the proguard configuration.
internal.proguardMapConsumer =
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 0affffd..86859f7 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -102,7 +102,9 @@
@Override
InternalOptions getInternalOptions() {
- return new InternalOptions();
+ InternalOptions internalOptions = new InternalOptions();
+ internalOptions.isRunningDeprecatedResourceShrinker = true;
+ return internalOptions;
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 72f5be9..f1058d6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -76,6 +76,16 @@
private FrameType() {}
}
+ @Override
+ public boolean isFrame() {
+ return true;
+ }
+
+ @Override
+ public CfFrame asFrame() {
+ return this;
+ }
+
private static class InitializedType extends FrameType {
private final DexType type;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 05f1181..041409b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -87,6 +87,14 @@
return false;
}
+ public CfFrame asFrame() {
+ return null;
+ }
+
+ public boolean isFrame() {
+ return false;
+ }
+
public CfPosition asPosition() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 1146ff2..b47570c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -115,6 +115,10 @@
!method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
}
+ public boolean isInvokeVirtual() {
+ return opcode == Opcodes.INVOKEVIRTUAL;
+ }
+
@Override
public boolean canThrow() {
return true;
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index ea056cb..b6b1f9c 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -163,6 +163,14 @@
return false;
}
+ public boolean isInvokeVirtual() {
+ return false;
+ }
+
+ public InvokeVirtual asInvokeVirtual() {
+ return null;
+ }
+
public boolean isSimpleNop() {
return !isPayload() && this instanceof Nop;
}
diff --git a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
index 003debf..b35882b 100644
--- a/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/code/InvokeVirtual.java
@@ -39,6 +39,16 @@
}
@Override
+ public boolean isInvokeVirtual() {
+ return true;
+ }
+
+ @Override
+ public InvokeVirtual asInvokeVirtual() {
+ return this;
+ }
+
+ @Override
public void registerUse(UseRegistry registry) {
registry.registerInvokeVirtual(getMethod());
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 8890a4d..ede2193 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -74,11 +74,11 @@
this.inputApp = inputApp;
}
- public DexApplication read() throws IOException, ExecutionException {
+ public DexApplication read() throws IOException {
return read((StringResource) null);
}
- public DexApplication read(StringResource proguardMap) throws IOException, ExecutionException {
+ public DexApplication read(StringResource proguardMap) throws IOException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
return read(proguardMap, executor);
@@ -87,14 +87,13 @@
}
}
- public final DexApplication read(ExecutorService executorService)
- throws IOException, ExecutionException {
+ public final DexApplication read(ExecutorService executorService) throws IOException {
return read(
null, executorService, ProgramClassCollection.defaultConflictResolver(options.reporter));
}
public final DexApplication read(StringResource proguardMap, ExecutorService executorService)
- throws IOException, ExecutionException {
+ throws IOException {
return read(
proguardMap,
executorService,
@@ -105,7 +104,7 @@
StringResource proguardMap,
ExecutorService executorService,
ProgramClassConflictResolver resolver)
- throws IOException, ExecutionException {
+ throws IOException {
assert verifyMainDexOptionsCompatible(inputApp, options);
Path dumpOutput = null;
boolean cleanDump = false;
@@ -167,7 +166,7 @@
}
private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) {
- app.dump(output, options.getProguardConfiguration(), options.reporter);
+ app.dump(output, options);
}
private static boolean verifyMainDexOptionsCompatible(
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 2e37abb..4dba5c6 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -668,7 +668,9 @@
annotationIterator.getNextFor(method),
parameterAnnotationsIterator.getNextFor(method),
code);
- if (accessFlags.isAbstract() && ensureNonAbstract) {
+ if (accessFlags.isAbstract()
+ && ensureNonAbstract
+ && !options.isRunningDeprecatedResourceShrinker) {
accessFlags.unsetAbstract();
encodedMethod =
options.isGeneratingClassFiles()
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index bdb6155..d9a5609 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.D8Command;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DexSplitterHelper;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.Keep;
import com.android.tools.r8.origin.PathOrigin;
@@ -186,8 +187,10 @@
}
// Shorthand error messages.
- public void error(String msg) {
- diagnosticsHandler.error(new StringDiagnostic(msg));
+ public Diagnostic error(String msg) {
+ StringDiagnostic error = new StringDiagnostic(msg);
+ diagnosticsHandler.error(error);
+ return error;
}
}
@@ -283,21 +286,18 @@
public static void run(Options options)
throws FeatureMappingException, CompilationFailedException {
- boolean errors = false;
+ Diagnostic error = null;
if (options.getInputArchives().isEmpty()) {
- errors = true;
- options.error("Need at least one --input");
+ error = options.error("Need at least one --input");
}
if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
- errors = true;
- options.error("You must supply a feature split mapping or feature jars");
+ error = options.error("You must supply a feature split mapping or feature jars");
}
if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
- errors = true;
- options.error("You can't supply both a feature split mapping and feature jars");
+ error = options.error("You can't supply both a feature split mapping and feature jars");
}
- if (errors) {
- throw new AbortException();
+ if (error != null) {
+ throw new AbortException(error);
}
D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
@@ -345,9 +345,9 @@
}
}
} catch (IOException e) {
- options.getDiagnosticsHandler().error(
- new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s))));
- throw new AbortException();
+ ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s)));
+ options.getDiagnosticsHandler().error(error);
+ throw new AbortException(error);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/errors/CompilationError.java b/src/main/java/com/android/tools/r8/errors/CompilationError.java
index ccefc7b..cb89d0b 100644
--- a/src/main/java/com/android/tools/r8/errors/CompilationError.java
+++ b/src/main/java/com/android/tools/r8/errors/CompilationError.java
@@ -30,6 +30,10 @@
this(message, null, origin);
}
+ public CompilationError(String message, Origin origin, Position position) {
+ this(message, null, origin, position);
+ }
+
public CompilationError(String message, Throwable cause, Origin origin) {
this(message, cause, origin, Position.UNKNOWN);
}
@@ -48,18 +52,6 @@
return position;
}
- public CompilationError withAdditionalOriginAndPositionInfo(Origin origin, Position position) {
- if (this.origin == Origin.unknown() || this.position == Position.UNKNOWN) {
- return new CompilationError(
- getMessage(),
- this,
- this.origin != Origin.unknown() ? this.origin : origin,
- this.position != Position.UNKNOWN ? this.position : position);
- } else {
- return this;
- }
- }
-
public Diagnostic toStringDiagnostic() {
return new StringDiagnostic(getMessage(), origin, position);
}
diff --git a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
index 59711e9..27b5a39 100644
--- a/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DesugarDiagnostic.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.errors;
import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.Keep;
/** Common interface type for all diagnostics related to desugaring. */
-@KeepForSubclassing
+@Keep
public interface DesugarDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
index 7bd18fd..afc2115 100644
--- a/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/InterfaceDesugarDiagnostic.java
@@ -3,10 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.errors;
-import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.Keep;
/** Common interface type for all diagnostics related to interface-method desugaring. */
-@KeepForSubclassing
-public interface InterfaceDesugarDiagnostic extends DesugarDiagnostic {
-
-}
+@Keep
+public interface InterfaceDesugarDiagnostic extends DesugarDiagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java b/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java
index 78f2f96..8662eee 100644
--- a/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/NestDesugarDiagnostic.java
@@ -4,11 +4,11 @@
package com.android.tools.r8.errors;
-import com.android.tools.r8.KeepForSubclassing;
+import com.android.tools.r8.Keep;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
-@KeepForSubclassing
+@Keep
public class NestDesugarDiagnostic implements DesugarDiagnostic {
private final Origin origin;
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
index 4a4db11..610104a 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -108,14 +108,12 @@
}
public boolean inBaseOrSameFeatureAs(DexProgramClass clazz, DexProgramClass context) {
- FeatureSplit split = javaTypeToFeatureSplitMapping.get(clazz.type.toSourceString());
- return split == null
- || split == javaTypeToFeatureSplitMapping.get(context.type.toSourceString());
+ FeatureSplit split = getFeatureSplit(clazz.type);
+ return split == null || split == getFeatureSplit(context.type);
}
public boolean isInFeature(DexProgramClass clazz) {
- return javaTypeToFeatureSplitMapping.containsKey(
- DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString()));
+ return getFeatureSplit(clazz.type) != null;
}
public boolean isInBase(DexProgramClass clazz) {
@@ -132,13 +130,19 @@
return true;
}
// TODO(141451259): Consider doing the mapping from DexType to Feature (with support in mapper)
- return javaTypeToFeatureSplitMapping.get(
- DescriptorUtils.descriptorToJavaType(a.toDescriptorString()))
- == javaTypeToFeatureSplitMapping.get(
- DescriptorUtils.descriptorToJavaType(b.toDescriptorString()));
+ return getFeatureSplit(a) == getFeatureSplit(b);
}
public List<FeatureSplit> getFeatureSplits() {
return featureSplits;
}
+
+ public FeatureSplit getFeatureSplitFromClassDescriptor(String classDescriptor) {
+ return javaTypeToFeatureSplitMapping.get(DescriptorUtils.descriptorToJavaType(classDescriptor));
+ }
+
+ private FeatureSplit getFeatureSplit(DexType type) {
+ assert type.isClassType();
+ return javaTypeToFeatureSplitMapping.get(type.toSourceString());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 2c05df2..854c5e7 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -15,6 +15,12 @@
public class AccessControl {
public static OptionalBool isClassAccessible(
+ DexClass clazz, ProgramMethod context, AppView<?> appView) {
+ return isClassAccessible(
+ clazz, context.getHolder(), appView.options().featureSplitConfiguration);
+ }
+
+ public static OptionalBool isClassAccessible(
DexClass clazz,
DexProgramClass context,
FeatureSplitConfiguration featureSplitConfiguration) {
@@ -40,6 +46,14 @@
public static OptionalBool isFieldAccessible(
DexEncodedField field,
DexClass holder,
+ ProgramMethod context,
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return isFieldAccessible(field, holder, context.getHolder(), appView.appInfo());
+ }
+
+ public static OptionalBool isFieldAccessible(
+ DexEncodedField field,
+ DexClass holder,
DexProgramClass context,
AppInfoWithClassHierarchy appInfo) {
return isMemberAccessible(field.accessFlags, holder, context, appInfo);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 0b6547e..8077269 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -26,8 +26,10 @@
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -67,6 +69,7 @@
private HorizontallyMergedLambdaClasses horizontallyMergedLambdaClasses;
private VerticallyMergedClasses verticallyMergedClasses;
private EnumValueInfoMapCollection unboxedEnums = EnumValueInfoMapCollection.empty();
+ private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
private Map<DexClass, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
@@ -96,7 +99,7 @@
this.options = options;
this.rewritePrefix = mapper;
- if (enableWholeProgramOptimizations() && options.isCallSiteOptimizationEnabled()) {
+ if (enableWholeProgramOptimizations() && options.callSiteOptimizationOptions().isEnabled()) {
this.callSiteOptimizationInfoPropagator =
new CallSiteOptimizationInfoPropagator(withLiveness());
} else {
@@ -372,6 +375,10 @@
this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
}
+ public void setCfByteCodePassThrough(Set<DexMethod> cfByteCodePassThrough) {
+ this.cfByteCodePassThrough = cfByteCodePassThrough;
+ }
+
public <U> U withInitializedClassesInInstanceMethods(
Function<InitializedClassesInInstanceMethods, U> fn, U defaultValue) {
if (initializedClassesInInstanceMethods != null) {
@@ -463,7 +470,17 @@
}
public boolean isCfByteCodePassThrough(DexEncodedMethod method) {
+ if (!options.isGeneratingClassFiles()) {
+ return false;
+ }
+ if (cfByteCodePassThrough.contains(method.method)) {
+ return true;
+ }
return options.testing.cfByteCodePassThrough != null
- && options.testing.cfByteCodePassThrough.test(method);
+ && options.testing.cfByteCodePassThrough.test(method.method);
+ }
+
+ public boolean hasCfByteCodePassThroughMethods() {
+ return !cfByteCodePassThrough.isEmpty();
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
index 158bf1f..7e46aad 100644
--- a/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/AppliedGraphLens.java
@@ -101,8 +101,10 @@
}
@Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
- return originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod);
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
+ return this != applied
+ ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod)
+ : originalMethod;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 9893684..1315f4a 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -160,7 +160,7 @@
ps.println("# Annotations:");
String prefix = "# ";
for (DexAnnotation annotation : annotations.annotations) {
- if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+ if (annotation.annotation.type == kotlin.factory.kotlinMetadataType) {
assert clazz != null : "Kotlin metadata is a class annotation";
KotlinMetadataWriter.writeKotlinMetadataAnnotation(prefix, annotation, ps, kotlin);
} else {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c2390fb..6e99fbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -131,7 +131,7 @@
// we need to maintain a set of states with (potentially different) contexts.
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
- private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.BOTTOM;
+ private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.bottom();
private int classFileVersion;
private KotlinMethodLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
@@ -783,6 +783,9 @@
assert !shouldNotHaveCode();
Builder builder = builder(this);
builder.setCode(buildEmptyThrowingDexCode());
+ if (isNonPrivateVirtualMethod()) {
+ builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
+ }
// Note that we are not marking this instance obsolete, since this util is only used by
// TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved
// again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s.
@@ -807,6 +810,9 @@
assert !shouldNotHaveCode();
Builder builder = builder(this);
builder.setCode(buildEmptyThrowingCfCode());
+ if (isNonPrivateVirtualMethod()) {
+ builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
+ }
// Note that we are not marking this instance obsolete:
// refer to Dex-backend version of this method above.
return builder.build();
@@ -1232,6 +1238,11 @@
optimizationInfo = info;
}
+ public synchronized void abandonCallSiteOptimizationInfo() {
+ checkIfObsolete();
+ callSiteOptimizationInfo = CallSiteOptimizationInfo.abandoned();
+ }
+
public synchronized CallSiteOptimizationInfo getCallSiteOptimizationInfo() {
checkIfObsolete();
return callSiteOptimizationInfo;
@@ -1263,6 +1274,7 @@
private DexMethod method;
private final MethodAccessFlags accessFlags;
private final DexAnnotationSet annotations;
+ private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
private ParameterAnnotationsList parameterAnnotations;
private Code code;
private CompilationState compilationState;
@@ -1301,6 +1313,12 @@
this.method = method;
}
+ public Builder setIsLibraryMethodOverride(OptionalBool isLibraryMethodOverride) {
+ assert !isLibraryMethodOverride.isUnknown();
+ this.isLibraryMethodOverride = isLibraryMethodOverride;
+ return this;
+ }
+
public Builder setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
this.parameterAnnotations = parameterAnnotations;
return this;
@@ -1381,6 +1399,9 @@
result.setKotlinMemberInfo(kotlinMemberInfo);
result.compilationState = compilationState;
result.optimizationInfo = optimizationInfo;
+ if (!isLibraryMethodOverride.isUnknown()) {
+ result.setLibraryMethodOverride(isLibraryMethodOverride);
+ }
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 7275598..3b635e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -269,6 +269,7 @@
public final DexString npeDescriptor = createString("Ljava/lang/NullPointerException;");
public final DexString reflectiveOperationExceptionDescriptor =
createString("Ljava/lang/ReflectiveOperationException;");
+ public final DexString kotlinMetadataDescriptor = createString("Lkotlin/Metadata;");
public final DexString intFieldUpdaterDescriptor =
createString("Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater;");
@@ -300,13 +301,13 @@
public final DexType voidType = createStaticallyKnownType(voidDescriptor);
public final DexType booleanArrayType = createStaticallyKnownType(booleanArrayDescriptor);
- public final DexType byteArrayType = createStaticallyKnownType(byteArrayDescriptor);
- public final DexType charArrayType = createStaticallyKnownType(charArrayDescriptor);
- public final DexType doubleArrayType = createStaticallyKnownType(doubleArrayDescriptor);
- public final DexType floatArrayType = createStaticallyKnownType(floatArrayDescriptor);
- public final DexType intArrayType = createStaticallyKnownType(intArrayDescriptor);
- public final DexType longArrayType = createStaticallyKnownType(longArrayDescriptor);
- public final DexType shortArrayType = createStaticallyKnownType(shortArrayDescriptor);
+ public final DexType byteArrayType = createStaticallyKnownType(byteArrayDescriptor);
+ public final DexType charArrayType = createStaticallyKnownType(charArrayDescriptor);
+ public final DexType doubleArrayType = createStaticallyKnownType(doubleArrayDescriptor);
+ public final DexType floatArrayType = createStaticallyKnownType(floatArrayDescriptor);
+ public final DexType intArrayType = createStaticallyKnownType(intArrayDescriptor);
+ public final DexType longArrayType = createStaticallyKnownType(longArrayDescriptor);
+ public final DexType shortArrayType = createStaticallyKnownType(shortArrayDescriptor);
public final DexType boxedBooleanType = createStaticallyKnownType(boxedBooleanDescriptor);
public final DexType boxedByteType = createStaticallyKnownType(boxedByteDescriptor);
@@ -399,6 +400,7 @@
public final DexType npeType = createStaticallyKnownType(npeDescriptor);
public final DexType reflectiveOperationExceptionType =
createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
+ public final DexType kotlinMetadataType = createStaticallyKnownType(kotlinMetadataDescriptor);
public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 03c329c..5f0a349 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -282,7 +282,7 @@
Map<DexType, DexClass> allClasses, Iterable<T> toAdd) {
for (DexClass clazz : toAdd) {
DexClass old = allClasses.put(clazz.type, clazz);
- assert old == null;
+ assert old == null : "Class " + old.type.toString() + " was already present.";
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index e5d7239..0d2390b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -49,6 +49,10 @@
this.resolvedField = resolvedField;
}
+ public DexClass getInitialResolutionHolder() {
+ return initialResolutionHolder;
+ }
+
public DexClass getResolvedHolder() {
return resolvedHolder;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 2a5f317..ea55ec4 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -140,12 +140,23 @@
public abstract DexField getRenamedFieldSignature(DexField originalField);
- public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
+ public final DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ return getRenamedMethodSignature(originalMethod, null);
+ }
+
+ public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied);
public DexEncodedMethod mapDexEncodedMethod(
DexEncodedMethod originalEncodedMethod, DexDefinitionSupplier definitions) {
+ return mapDexEncodedMethod(originalEncodedMethod, definitions, null);
+ }
+
+ public DexEncodedMethod mapDexEncodedMethod(
+ DexEncodedMethod originalEncodedMethod,
+ DexDefinitionSupplier definitions,
+ GraphLense applied) {
assert originalEncodedMethod != DexEncodedMethod.SENTINEL;
- DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
+ DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method, applied);
// Note that:
// * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
// up, since `originalEncodedMethod` may be obsolete.
@@ -445,7 +456,7 @@
}
@Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
return originalMethod;
}
@@ -511,8 +522,10 @@
}
@Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
- return previous.getRenamedMethodSignature(originalMethod);
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
+ return this != applied
+ ? previous.getRenamedMethodSignature(originalMethod, applied)
+ : originalMethod;
}
@Override
@@ -611,8 +624,11 @@
}
@Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
- DexMethod renamedMethod = previousLense.getRenamedMethodSignature(originalMethod);
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
+ if (this == applied) {
+ return originalMethod;
+ }
+ DexMethod renamedMethod = previousLense.getRenamedMethodSignature(originalMethod, applied);
return originalMethodSignatures != null
? originalMethodSignatures.inverse().getOrDefault(renamedMethod, renamedMethod)
: renamedMethod;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 0b5795d..1f04cca 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
@@ -86,6 +87,11 @@
}
public void read(Origin origin, ClassKind classKind, byte[] bytes) {
+ ExceptionUtils.withOriginAttachmentHandler(
+ origin, () -> internalRead(origin, classKind, bytes));
+ }
+
+ public void internalRead(Origin origin, ClassKind classKind, byte[] bytes) {
if (bytes.length < CLASSFILE_HEADER.length) {
throw new CompilationError("Invalid empty classfile", origin);
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index d17a4d2..e7a799d 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -372,8 +372,10 @@
// This code visitor is used only if the method is neither abstract nor native, hence it
// should have exactly one Code attribute:
// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3
- throw new CompilationError("Absent Code attribute in method that is not native or abstract")
- .withAdditionalOriginAndPositionInfo(origin, new MethodPosition(method));
+ throw new CompilationError(
+ "Absent Code attribute in method that is not native or abstract",
+ origin,
+ new MethodPosition(method));
}
code.setCode(
new CfCode(
@@ -988,7 +990,8 @@
private static DebugParsingOptions getParsingOptions(
JarApplicationReader application, boolean reachabilitySensitive) {
int parsingOptions =
- application.options.testing.readInputStackMaps
+ (application.options.enableCfByteCodePassThrough
+ || application.options.testing.readInputStackMaps)
? ClassReader.EXPAND_FRAMES
: ClassReader.SKIP_FRAMES;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 5a5db62..c7edad2 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -8,6 +8,7 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -121,6 +122,23 @@
return null;
}
+ @Override
+ void removeMethods(Set<DexEncodedMethod> methods) {
+ directMethods = removeMethodsHelper(methods, directMethods);
+ virtualMethods = removeMethodsHelper(methods, virtualMethods);
+ }
+
+ private static DexEncodedMethod[] removeMethodsHelper(
+ Set<DexEncodedMethod> methodsToRemove, DexEncodedMethod[] existingMethods) {
+ List<DexEncodedMethod> newMethods = new ArrayList<>(existingMethods.length);
+ for (DexEncodedMethod method : existingMethods) {
+ if (!methodsToRemove.contains(method)) {
+ newMethods.add(method);
+ }
+ }
+ return newMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+ }
+
private DexEncodedMethod removeMethodWithIndex(
int index, DexEncodedMethod[] methods, Consumer<DexEncodedMethod[]> newMethodsConsumer) {
DexEncodedMethod removed = methods[index];
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 14655d5..c93d366 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -259,6 +259,12 @@
return removed;
}
+ public void removeMethods(Set<DexEncodedMethod> methods) {
+ backing.removeMethods(methods);
+ resetDirectMethodCaches();
+ resetVirtualMethodCaches();
+ }
+
public void setDirectMethods(DexEncodedMethod[] methods) {
assert verifyCorrectnessOfMethodHolders(methods);
resetDirectMethodCaches();
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 2ce0cd8..0b3f435 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -96,6 +96,8 @@
abstract DexEncodedMethod removeMethod(DexMethod method);
+ abstract void removeMethods(Set<DexEncodedMethod> method);
+
// Replacement/mutation methods.
abstract void setDirectMethods(DexEncodedMethod[] methods);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index 18257c1..19c3cec 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -207,6 +207,11 @@
}
@Override
+ void removeMethods(Set<DexEncodedMethod> methods) {
+ methods.forEach(method -> methodMap.remove(wrap(method.getReference())));
+ }
+
+ @Override
void setDirectMethods(DexEncodedMethod[] methods) {
if ((methods == null || methods.length == 0) && methodMap.isEmpty()) {
return;
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 0f63ed1..e676697 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -13,7 +13,6 @@
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
-import java.util.concurrent.ExecutionException;
public class SmaliWriter extends DexByteCodeWriter {
@@ -30,7 +29,7 @@
new ApplicationReader(application, options, Timing.empty()).read();
SmaliWriter writer = new SmaliWriter(dexApplication, options);
writer.write(ps);
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new CompilationError("Failed to generate smali sting", e);
}
return new String(os.toByteArray(), StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index 6b03d33..c57a3da 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -167,8 +167,8 @@
i < code.instructions.size() && nextExpectedInstructionIndex < sequence.size();
i++) {
instruction = code.instructions.get(i);
- if (instruction.isLabel()) {
- // Just ignore labels.
+ if (instruction.isLabel() || instruction.isFrame()) {
+ // Just ignore labels and frames.
continue;
}
if (instruction.getClass() != sequence.get(nextExpectedInstructionIndex)) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 049e211..1a00007 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.graph.analysis;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
@@ -18,8 +17,7 @@
public void processNewlyInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {}
/** Called when a class is found to be live. */
- public void processNewlyLiveClass(
- DexProgramClass clazz, EnqueuerWorklist worklist, DexDefinitionSupplier definitionSupplier) {}
+ public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
/** Called when a field is found to be live. */
public void processNewlyLiveField(ProgramField field) {}
@@ -37,5 +35,5 @@
* Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
* perform some post-processing.
*/
- public void done() {}
+ public void done(Enqueuer enqueuer) {}
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 59c37b8..cc74d21 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.shaking.Enqueuer;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -84,7 +85,7 @@
}
@Override
- public void done() {
+ public void done(Enqueuer enqueuer) {
appView.setInitializedClassesInInstanceMethods(
new InitializedClassesInInstanceMethods(appView, mapping));
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java b/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java
deleted file mode 100644
index 937fe2a..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/AbstractError.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2019, 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.ir.analysis;
-
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-
-// Note that this is not what D8/R8 can throw.
-//
-// Finer-grained abstraction of Throwable that an instruction can throw if any.
-//
-// Top // throwing, but not quite sure what it would be.
-// / | \
-// NPE ICCE ... // specific exceptions.
-// \ | /
-// Bottom // not throwing.
-public class AbstractError {
-
- private static final AbstractError TOP = new AbstractError();
- private static final AbstractError BOTTOM = new AbstractError();
-
- private DexType simulatedError;
-
- private AbstractError() {}
-
- private AbstractError(DexType throwable) {
- simulatedError = throwable;
- }
-
- public static AbstractError top() {
- return TOP;
- }
-
- public static AbstractError bottom() {
- return BOTTOM;
- }
-
- public static AbstractError specific(DexType throwable) {
- return new AbstractError(throwable);
- }
-
- public boolean cannotThrow() {
- return this == BOTTOM;
- }
-
- public boolean isThrowing() {
- return this != BOTTOM;
- }
-
- public DexType getSpecificError(DexItemFactory factory) {
- assert isThrowing();
- if (simulatedError != null) {
- return simulatedError;
- }
- assert this == TOP;
- return factory.throwableType;
- }
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 82e53b7..fbb42a9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -388,7 +388,7 @@
if (instruction.isStaticPut()) {
StaticPut otherStaticPut = instruction.asStaticPut();
if (otherStaticPut.getField().holder == staticPut.getField().holder
- && instruction.instructionInstanceCanThrow(appView, context).cannotThrow()) {
+ && !instruction.instructionInstanceCanThrow(appView, context)) {
continue;
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index b5bf1c4..dd450d6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -66,13 +66,14 @@
return;
}
+ timing.begin("Enqueue methods for reprocessing");
+ enqueueMethodsForReprocessing(appInfo, executorService);
+ timing.end(); // Enqueue methods for reprocessing
+
timing.begin("Clear reads from fields of interest");
clearReadsFromFieldsOfInterest(appInfo);
timing.end(); // Clear reads from fields of interest
- timing.begin("Enqueue methods for reprocessing");
- enqueueMethodsForReprocessing(appInfo, executorService);
- timing.end(); // Enqueue methods for reprocessing
timing.end(); // Trivial field accesses analysis
fieldsOfInterest.forEach(OptimizationFeedbackSimple.getInstance()::markFieldAsDead);
@@ -164,6 +165,11 @@
private boolean registerFieldAccess(DexField field, boolean isStatic) {
DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
if (encodedField != null) {
+ // We cannot remove references from pass through functions.
+ if (appView.isCfByteCodePassThrough(method.getDefinition())) {
+ fieldsOfInterest.remove(encodedField);
+ return true;
+ }
if (encodedField.isStatic() == isStatic) {
if (fieldsOfInterest.contains(encodedField)) {
methodsToReprocess.add(method);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index d1bf3d1..c44747f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -97,8 +96,7 @@
}
@Override
- public void processNewlyLiveClass(
- DexProgramClass clazz, EnqueuerWorklist worklist, DexDefinitionSupplier definitionSupplier) {
+ public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
assert appView.appInfo().hasClassHierarchy();
AppInfoWithClassHierarchy appInfo = appView.appInfo().withClassHierarchy();
if (appInfo.isStrictSubtypeOf(clazz.type, references.generatedMessageLiteType)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index f92edd0..136c209 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -56,7 +56,7 @@
|| !array.definition.isCreatingArray()
|| environmentAnalysis.valueMayDependOnEnvironment(arrayPut.index())
|| environmentAnalysis.valueMayDependOnEnvironment(arrayPut.value())
- || arrayPut.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ || arrayPut.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
if (controlFlowMayDependOnEnvironment.isUnknown()) {
@@ -75,7 +75,7 @@
Value array = newArrayFilledData.src();
if (array.isPhi()
|| !array.definition.isCreatingArray()
- || newArrayFilledData.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ || newArrayFilledData.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
if (controlFlowMayDependOnEnvironment.isUnknown()) {
@@ -92,7 +92,7 @@
// on the environment.
if (instruction.isInvokeNewArray()) {
InvokeNewArray invokeNewArray = instruction.asInvokeNewArray();
- if (invokeNewArray.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ if (invokeNewArray.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
for (Value argument : invokeNewArray.arguments()) {
@@ -104,7 +104,7 @@
}
if (instruction.isNewArrayEmpty()) {
- if (instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ if (instruction.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
continue;
@@ -117,7 +117,7 @@
if (field == null
|| field.holder() != context.getHolderType()
|| environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
- || instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ || instruction.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
mayHaveSideEffects = true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index fcea78c..e9f074b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -74,18 +73,14 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
- if (array().type.isNullable()) {
- return AbstractError.specific(appView.dexItemFactory().npeType);
- }
-
- return AbstractError.bottom();
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ return array().type.isNullable();
}
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionInstanceCanThrow(appView, context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
index d9fc837..523ca73 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockIterator.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.utils.IteratorUtils;
import java.util.ListIterator;
public class BasicBlockIterator implements ListIterator<BasicBlock> {
@@ -22,6 +23,14 @@
this.listIterator = code.blocks.listIterator(index);
}
+ public BasicBlock peekPrevious() {
+ return IteratorUtils.peekPrevious(this);
+ }
+
+ public BasicBlock peekNext() {
+ return IteratorUtils.peekNext(this);
+ }
+
@Override
public boolean hasNext() {
return listIterator.hasNext();
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index cdbf6f0..35767cc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.ir.code;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
@@ -13,11 +12,11 @@
import com.android.tools.r8.code.MoveObject;
import com.android.tools.r8.code.MoveObjectFrom16;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -98,27 +97,27 @@
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionInstanceCanThrow(appView, context);
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
if (appView.options().debug || !appView.appInfo().hasLiveness()) {
- return AbstractError.top();
+ return true;
}
if (type.isPrimitiveType()) {
- return AbstractError.top();
+ return true;
}
DexType baseType = type.toBaseType(appView.dexItemFactory());
if (baseType.isClassType()) {
- DexClass dexClass = appView.definitionFor(baseType);
- // * NoClassDefFoundError (resolution failure).
- if (dexClass == null || !dexClass.isResolvable(appView)) {
- return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+ DexClass definition = appView.definitionFor(baseType);
+ // Check that the class and its super types are present.
+ if (definition == null || !definition.isResolvable(appView)) {
+ return true;
}
- // * IllegalAccessError (not visible from the access context).
- if (!isClassTypeVisibleFromContext(appView, context, dexClass)) {
- return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
+ // Check that the class is accessible.
+ if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+ return true;
}
}
AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
@@ -128,9 +127,9 @@
.lessThanOrEqualUpToNullability(castType, appView)) {
// This is a check-cast that has to be there for bytecode correctness, but R8 has proven
// that this cast will never throw.
- return AbstractError.bottom();
+ return false;
}
- return AbstractError.top();
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 759e764..4594db9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -4,17 +4,15 @@
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -101,42 +99,37 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
DexType baseType = getValue().toBaseType(appView.dexItemFactory());
if (baseType.isPrimitiveType()) {
- return AbstractError.bottom();
+ return false;
}
// Not applicable for D8.
if (!appView.enableWholeProgramOptimizations()) {
// Unless the type of interest is same as the context.
if (baseType == context.getHolderType()) {
- return AbstractError.bottom();
+ return false;
}
- return AbstractError.top();
+ return true;
}
DexClass clazz = appView.definitionFor(baseType);
-
- if (clazz == null) {
- return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+ // * Check that the class and its super types are present.
+ if (clazz == null || !clazz.isResolvable(appView)) {
+ return true;
}
- // * NoClassDefFoundError (resolution failure).
- if (!clazz.isResolvable(appView)) {
- return AbstractError.specific(appView.dexItemFactory().noClassDefFoundErrorType);
+ // * Check that the class is accessible.
+ if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+ return true;
}
- // * IllegalAccessError (not visible from the access context).
- if (!isClassTypeVisibleFromContext(appView, context, clazz)) {
- return AbstractError.specific(appView.dexItemFactory().illegalAccessErrorType);
- }
-
- return AbstractError.bottom();
+ return false;
}
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionInstanceCanThrow(appView, context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 120a2d4..20eadc9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -168,8 +168,12 @@
/** Returns the blocks dominated by dominator, including dominator itself. */
public List<BasicBlock> dominatedBlocks(BasicBlock dominator) {
+ return dominatedBlocks(dominator, new ArrayList<>());
+ }
+
+ public <T extends Collection<BasicBlock>> T dominatedBlocks(
+ BasicBlock dominator, T dominatedBlocks) {
assert !obsolete;
- List<BasicBlock> dominatedBlocks = new ArrayList<>();
for (int i = dominator.getNumber(); i < unreachableStartIndex; ++i) {
BasicBlock block = sorted[i];
if (dominatedBy(block, dominator)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 436de09..fde9019 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
@@ -60,28 +59,28 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
return instructionInstanceCanThrow(appView, context, SideEffectAssumption.NONE);
}
- public AbstractError instructionInstanceCanThrow(
+ public boolean instructionInstanceCanThrow(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
SuccessfulFieldResolutionResult resolutionResult =
appView.appInfo().resolveField(field, context).asSuccessfulResolution();
if (resolutionResult == null) {
- return AbstractError.top();
+ return true;
}
DexEncodedField resolvedField = resolutionResult.getResolvedField();
// Check if the instruction may fail with an IncompatibleClassChangeError.
if (resolvedField.isStatic() != isStaticFieldInstruction()) {
- return AbstractError.top();
+ return true;
}
// Check if the resolution target is accessible.
if (resolutionResult.getResolvedHolder() != context.getHolder()) {
if (resolutionResult
.isAccessibleFrom(context, appView.appInfo().withClassHierarchy())
.isPossiblyFalse()) {
- return AbstractError.top();
+ return true;
}
}
// TODO(b/137168535): Without non-null tracking, only locally created receiver is allowed in D8.
@@ -90,14 +89,14 @@
if (!assumption.canAssumeReceiverIsNotNull()) {
Value receiver = inValues.get(0);
if (receiver.isAlwaysNull(appView) || receiver.type.isNullable()) {
- return AbstractError.specific(appView.dexItemFactory().npeType);
+ return true;
}
}
}
// For D8, reaching here means the field is in the same context, hence the class is guaranteed
// to be initialized already.
if (!appView.enableWholeProgramOptimizations()) {
- return AbstractError.bottom();
+ return false;
}
boolean mayTriggerClassInitialization =
isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
@@ -106,7 +105,7 @@
if (appView.appInfo().hasLiveness()) {
AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.field)) {
- return AbstractError.bottom();
+ return false;
}
}
// May trigger <clinit> that may have side effects.
@@ -115,10 +114,10 @@
// Types that are a super type of `context` are guaranteed to be initialized already.
type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
Sets.newIdentityHashSet())) {
- return AbstractError.top();
+ return true;
}
}
- return AbstractError.bottom();
+ return false;
}
@Override
@@ -172,7 +171,7 @@
AbstractValue abstractValue = field.getOptimizationInfo().getAbstractValue();
if (abstractValue.isSingleValue()) {
- if (abstractValue.isZero()) {
+ if (abstractValue.isSingleConstValue()) {
return false;
}
if (abstractValue.isSingleFieldValue()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 42068de..706d470 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -89,6 +89,14 @@
return visitor.visit(this);
}
+ public boolean isNullTest() {
+ return isZeroTest() && lhs().getType().isReferenceType();
+ }
+
+ public boolean isNonTrivialNullTest() {
+ return isNullTest() && lhs().getType().isNullable();
+ }
+
public boolean isZeroTest() {
return inValues.size() == 1;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 3ff5c53..d24dce8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -4,16 +4,15 @@
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.code.CfInitClass;
import com.android.tools.r8.code.DexInitClass;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -94,24 +93,30 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
- if (!isTypeVisibleFromContext(appView, context, clazz)) {
- return AbstractError.top();
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ DexClass definition = appView.definitionFor(clazz);
+ // * Check that the class is present.
+ if (definition == null) {
+ return true;
+ }
+ // * Check that the class is accessible.
+ if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
+ return true;
}
if (clazz.classInitializationMayHaveSideEffects(
appView,
// Types that are a super type of `context` are guaranteed to be initialized already.
type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
Sets.newIdentityHashSet())) {
- return AbstractError.top();
+ return true;
}
- return AbstractError.bottom();
+ return false;
}
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionInstanceCanThrow(appView, context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 38c1813..924d256 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -119,7 +119,7 @@
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
- return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
+ return instructionInstanceCanThrow(appView, context, assumption);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index d5aad2d..115afec 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -121,7 +121,7 @@
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
- if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
+ if (instructionInstanceCanThrow(appView, context, assumption)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 5fa8d11..6d298df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
import com.android.tools.r8.ir.analysis.constant.Bottom;
@@ -590,8 +589,8 @@
public abstract boolean instructionMayTriggerMethodInvocation(
AppView<?> appView, ProgramMethod context);
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
- return instructionInstanceCanThrow() ? AbstractError.top() : AbstractError.bottom();
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ return instructionInstanceCanThrow();
}
/** Returns true is this instruction can be treated as dead code if its outputs are not used. */
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 950d1bc..496f869 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -33,6 +33,18 @@
super(target, result, arguments);
}
+ public boolean hasRefinedReceiverLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+ assert isInvokeMethodWithDynamicDispatch();
+ return getReceiver().getDynamicLowerBoundType(appView) != null;
+ }
+
+ public boolean hasRefinedReceiverUpperBoundType(AppView<AppInfoWithLiveness> appView) {
+ assert isInvokeMethodWithDynamicDispatch();
+ DexType staticReceiverType = getInvokedMethod().holder;
+ DexType refinedReceiverType = TypeAnalysis.getRefinedReceiverType(appView, this);
+ return refinedReceiverType != staticReceiverType;
+ }
+
@Override
public boolean isInvokeMethodWithReceiver() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 1e41503..cc83815 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -3,17 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfMultiANewArray;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -114,7 +112,7 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
if (baseType.isPrimitiveType()) {
// Primitives types are known to be present and accessible.
@@ -132,30 +130,30 @@
if (!appView.enableWholeProgramOptimizations()) {
// Conservatively bail-out in D8, because we require whole program knowledge to determine if
// the type is present and accessible.
- return AbstractError.top();
+ return true;
}
// Check if the type is guaranteed to be present.
DexClass clazz = appView.definitionFor(baseType);
if (clazz == null) {
- return AbstractError.top();
+ return true;
}
if (clazz.isLibraryClass()
&& !appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
- return AbstractError.top();
+ return true;
}
// Check if the type is guaranteed to be accessible.
- if (!isClassTypeVisibleFromContext(appView, context, clazz)) {
- return AbstractError.top();
+ if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+ return true;
}
// The type is known to be present and accessible.
return instructionInstanceCanThrowNegativeArraySizeException();
}
- private AbstractError instructionInstanceCanThrowNegativeArraySizeException() {
+ private boolean instructionInstanceCanThrowNegativeArraySizeException() {
boolean mayHaveNegativeArraySize = false;
for (Value value : arguments()) {
if (!value.hasValueRange()) {
@@ -168,7 +166,7 @@
break;
}
}
- return mayHaveNegativeArraySize ? AbstractError.top() : AbstractError.bottom();
+ return mayHaveNegativeArraySize;
}
@Override
@@ -180,7 +178,7 @@
return true;
}
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionInstanceCanThrow(appView, context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 1d6547e..fc6028f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -3,18 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.code.FilledNewArray;
import com.android.tools.r8.code.FilledNewArrayRange;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -141,47 +139,47 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
if (baseType.isPrimitiveType()) {
// Primitives types are known to be present and accessible.
assert !type.isWideType() : "The array's contents must be single-word";
- return AbstractError.bottom();
+ return false;
}
assert baseType.isReferenceType();
if (baseType == context.getHolderType()) {
// The enclosing type is known to be present and accessible.
- return AbstractError.bottom();
+ return false;
}
if (!appView.enableWholeProgramOptimizations()) {
// Conservatively bail-out in D8, because we require whole program knowledge to determine if
// the type is present and accessible.
- return AbstractError.top();
+ return true;
}
// Check if the type is guaranteed to be present.
DexClass clazz = appView.definitionFor(baseType);
if (clazz == null) {
- return AbstractError.top();
+ return true;
}
if (clazz.isLibraryClass()) {
if (!appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
- return AbstractError.top();
+ return true;
}
}
// Check if the type is guaranteed to be accessible.
- if (!isClassTypeVisibleFromContext(appView, context, clazz)) {
- return AbstractError.top();
+ if (AccessControl.isClassAccessible(clazz, context, appView).isPossiblyFalse()) {
+ return true;
}
// Note: Implicitly assuming that all the arguments are of the right type, because the input
// code must be valid.
- return AbstractError.bottom();
+ return false;
}
@Override
@@ -193,7 +191,7 @@
return true;
}
- return instructionInstanceCanThrow(appView, context).isThrowing();
+ return instructionInstanceCanThrow(appView, context);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 2c5faaf..7cd9b1e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokeStaticRange;
import com.android.tools.r8.graph.AppView;
@@ -13,6 +11,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -31,16 +30,16 @@
public class InvokeStatic extends InvokeMethod {
- private final boolean itf;
+ private final boolean isInterface;
public InvokeStatic(DexMethod target, Value result, List<Value> arguments) {
this(target, result, arguments, false);
assert target.proto.parameters.size() == arguments.size();
}
- public InvokeStatic(DexMethod target, Value result, List<Value> arguments, boolean itf) {
+ public InvokeStatic(DexMethod target, Value result, List<Value> arguments, boolean isInterface) {
super(target, result, arguments);
- this.itf = itf;
+ this.isInterface = isInterface;
}
@Override
@@ -142,7 +141,8 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, getInvokedMethod(), itf));
+ builder.add(
+ new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, getInvokedMethod(), isInterface));
}
@Override
@@ -175,43 +175,54 @@
}
// Find the target and check if the invoke may have side effects.
- if (appView.appInfo().hasLiveness()) {
- AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
- if (target == null) {
- return true;
- }
-
- // Verify that the target method is accessible in the current context.
- if (!isMemberVisibleFromOriginalContext(
- appView, context, target.holder(), target.accessFlags)) {
- return true;
- }
-
- // Verify that the target method does not have side-effects.
- if (appViewWithLiveness.appInfo().noSideEffects.containsKey(target.method)) {
- return false;
- }
-
- if (target.getOptimizationInfo().mayHaveSideEffects()) {
- return true;
- }
-
- if (assumption.canAssumeClassIsAlreadyInitialized()) {
- return false;
- }
-
- return target
- .holder()
- .classInitializationMayHaveSideEffects(
- appView,
- // Types that are a super type of `context` are guaranteed to be initialized
- // already.
- type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
- Sets.newIdentityHashSet());
+ if (!appView.appInfo().hasLiveness()) {
+ return true;
}
- return true;
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
+
+ SingleResolutionResult resolutionResult =
+ appViewWithLiveness
+ .appInfo()
+ .resolveMethod(getInvokedMethod(), isInterface)
+ .asSingleResolution();
+
+ // Verify that the target method is present.
+ if (resolutionResult == null) {
+ return true;
+ }
+
+ DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+ assert singleTarget != null;
+
+ // Verify that the target method is static and accessible.
+ if (!singleTarget.isStatic()
+ || resolutionResult.isAccessibleFrom(context, appInfoWithLiveness).isPossiblyFalse()) {
+ return true;
+ }
+
+ // Verify that the target method does not have side-effects.
+ if (appViewWithLiveness.appInfo().noSideEffects.containsKey(singleTarget.getReference())) {
+ return false;
+ }
+
+ if (singleTarget.getOptimizationInfo().mayHaveSideEffects()) {
+ return true;
+ }
+
+ if (assumption.canAssumeClassIsAlreadyInitialized()) {
+ return false;
+ }
+
+ return singleTarget
+ .holder()
+ .classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of `context` are guaranteed to be initialized
+ // already.
+ type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type),
+ Sets.newIdentityHashSet());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LazyDominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/LazyDominatorTree.java
new file mode 100644
index 0000000..bcb0fff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/LazyDominatorTree.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2020, 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.ir.code;
+
+import com.android.tools.r8.ir.code.DominatorTree.Assumption;
+import com.android.tools.r8.utils.Box;
+
+public class LazyDominatorTree extends Box<DominatorTree> {
+
+ private final IRCode code;
+
+ public LazyDominatorTree(IRCode code) {
+ this.code = code;
+ }
+
+ @Override
+ public DominatorTree get() {
+ return computeIfAbsent(() -> new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index aaa27dd..54d1ad4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -116,24 +115,15 @@
}
@Override
- public AbstractError instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
- if (appView.options().debug) {
- return AbstractError.top();
- }
-
- if (src().getType().isNullable()) {
- return AbstractError.top();
- }
-
- return AbstractError.bottom();
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ return appView.options().debug || src().getType().isNullable();
}
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
// Treat the instruction as possibly having side-effects if it may throw or the array is used.
- if (instructionInstanceCanThrow(appView, context).isThrowing()
- || src().numberOfAllUsers() > 1) {
+ if (instructionInstanceCanThrow(appView, context) || src().numberOfAllUsers() > 1) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 3e6c121..c1b2bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -3,12 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
@@ -170,8 +169,7 @@
}
// Verify that the instruction does not lead to an IllegalAccessError.
- if (!isMemberVisibleFromOriginalContext(
- appView, context, definition.type, definition.accessFlags)) {
+ if (AccessControl.isClassAccessible(definition, context, appView).isPossiblyFalse()) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index e0e8b63..4dc7c12 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -145,7 +145,7 @@
@Override
public boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
- return instructionInstanceCanThrow(appView, context, assumption).isThrowing();
+ return instructionInstanceCanThrow(appView, context, assumption);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 11732a9..678a569 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -110,7 +110,7 @@
return false;
}
- if (instructionInstanceCanThrow(appView, context, assumption).isThrowing()) {
+ if (instructionInstanceCanThrow(appView, context, assumption)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 02ab2d9..f1a6b89 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -592,13 +592,17 @@
public void clearUsers() {
users.clear();
uniqueUsers = null;
- phiUsers.clear();
- uniquePhiUsers = null;
+ clearPhiUsers();
if (debugData != null) {
debugData.users.clear();
}
}
+ public void clearPhiUsers() {
+ phiUsers.clear();
+ uniquePhiUsers = null;
+ }
+
public void addPhiUser(Phi user) {
phiUsers.add(user);
uniquePhiUsers = null;
@@ -688,6 +692,28 @@
clearUsers();
}
+ public void replacePhiUsers(Value newValue) {
+ if (this == newValue) {
+ return;
+ }
+ for (Phi user : uniquePhiUsers()) {
+ user.replaceOperand(this, newValue);
+ }
+ clearPhiUsers();
+ }
+
+ public void replaceSelectiveInstructionUsers(Value newValue, Predicate<Instruction> predicate) {
+ if (this == newValue) {
+ return;
+ }
+ for (Instruction user : uniqueUsers()) {
+ if (predicate.test(user)) {
+ fullyRemoveUser(user);
+ user.replaceValue(this, newValue);
+ }
+ }
+ }
+
public void replaceSelectiveUsers(
Value newValue,
Set<Instruction> selectedInstructions,
@@ -1129,6 +1155,10 @@
setType(newType);
}
+ public BasicBlock getBlock() {
+ return definition.getBlock();
+ }
+
public TypeElement getType() {
return type;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9822c48..ecd971f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -51,7 +51,6 @@
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.StringConcatRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.ir.optimize.AliasIntroducer;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.Assumer;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
@@ -288,9 +287,6 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
: null;
- if (options.testing.forceAssumeNoneInsertion) {
- assumers.add(new AliasIntroducer(appView));
- }
if (appView.enableWholeProgramOptimizations()) {
assert appView.appInfo().hasLiveness();
assert appView.rootSet() != null;
@@ -477,10 +473,10 @@
}
}
- private void synthesizeEnumUnboxingUtilityClass(
+ private void synthesizeEnumUnboxingUtilityMethods(
Builder<?> builder, ExecutorService executorService) throws ExecutionException {
if (enumUnboxer != null) {
- enumUnboxer.synthesizeUtilityClass(builder, this, executorService);
+ enumUnboxer.synthesizeUtilityMethods(builder, this, executorService);
}
}
@@ -730,8 +726,7 @@
}
if (enumUnboxer != null) {
enumUnboxer.finishAnalysis();
- enumUnboxer.unboxEnums(
- postMethodProcessorBuilder, executorService, feedback, classStaticizer);
+ enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
}
new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
.run(executorService, feedback, timing);
@@ -779,7 +774,7 @@
synthesizeJava8UtilityClass(builder, executorService);
synthesizeRetargetClass(builder, executorService);
handleSynthesizedClassMapping(builder);
- synthesizeEnumUnboxingUtilityClass(builder, executorService);
+ synthesizeEnumUnboxingUtilityMethods(builder, executorService);
printPhase("Lambda merging finalization");
// TODO(b/127694949): Adapt to PostOptimization.
@@ -1102,7 +1097,7 @@
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
MethodProcessingId methodProcessingId) {
- return ExceptionUtils.withOriginAttachmentHandler(
+ return ExceptionUtils.withOriginAndPositionAttachmentHandler(
method.getOrigin(),
new MethodPosition(method.getReference()),
() -> rewriteCodeInternal(method, feedback, methodProcessor, methodProcessingId));
@@ -1124,6 +1119,9 @@
method.toSourceString(),
logCode(options, method.getDefinition()));
}
+ if (options.testing.hookInIrConversion != null) {
+ options.testing.hookInIrConversion.run();
+ }
if (options.skipIR) {
feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
return Timing.empty();
@@ -1218,6 +1216,16 @@
assert code.verifyTypes(appView);
assert code.isConsistentSSA();
+ if (appView.isCfByteCodePassThrough(method)) {
+ // If the code is pass trough, do not finalize by overwriting the existing code.
+ assert appView.enableWholeProgramOptimizations();
+ timing.begin("Collect optimization info");
+ collectOptimizationInfo(
+ method, code, ClassInitializerDefaultsResult.empty(), feedback, methodProcessor, timing);
+ timing.end();
+ return timing;
+ }
+
assertionsRewriter.run(method, code, timing);
if (serviceLoaderRewriter != null) {
@@ -1608,11 +1616,6 @@
timing.end();
}
- if (appView.isCfByteCodePassThrough(method)) {
- // If the code is pass trough, do not finalize by overwriting the existing code.
- return timing;
- }
-
printMethod(code, "Optimized IR (SSA)", previous);
timing.begin("Finalize IR");
finalizeIR(code, feedback, timing);
@@ -1648,7 +1651,7 @@
boolean isDebugMode = options.debug || method.getOptimizationInfo().isReachabilitySensitive();
if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) {
timing.begin("Collect call-site info");
- appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code);
+ appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code, timing);
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 0b2476b..fbeff68 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodProcessingId.Factory.ReservedMethodProcessingIds;
@@ -60,8 +61,8 @@
public static class Builder {
private final Collection<CodeOptimization> defaultCodeOptimizations;
- private final LongLivedProgramMethodSetBuilder methodsMap =
- new LongLivedProgramMethodSetBuilder();
+ private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
+ LongLivedProgramMethodSetBuilder.create();
private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
new IdentityHashMap<>();
@@ -76,7 +77,7 @@
return;
}
for (ProgramMethod method : methodsToRevisit) {
- methodsMap.add(method);
+ methodsToReprocess.add(method);
optimizationsMap
.computeIfAbsent(
method.getDefinition(),
@@ -102,13 +103,15 @@
// Some optimizations may change methods, creating new instances of the encoded methods with a
// new signature. The compiler needs to update the set of methods that must be reprocessed
// according to the graph lens.
- public void mapDexEncodedMethods(AppView<?> appView) {
+ public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
+ methodsToReprocess.rewrittenWithLens(appView, applied);
Map<DexEncodedMethod, Collection<CodeOptimization>> newOptimizationsMap =
new IdentityHashMap<>();
optimizationsMap.forEach(
(method, optimizations) ->
newOptimizationsMap.put(
- appView.graphLense().mapDexEncodedMethod(method, appView), optimizations));
+ appView.graphLense().mapDexEncodedMethod(method, appView, applied),
+ optimizations));
optimizationsMap.clear();
optimizationsMap.putAll(newOptimizationsMap);
}
@@ -132,12 +135,13 @@
});
put(set);
}
- if (methodsMap.isEmpty()) {
+ if (methodsToReprocess.isEmpty()) {
// Nothing to revisit.
return null;
}
CallGraph callGraph =
- new PartialCallGraphBuilder(appView, methodsMap.build(appView))
+ new PartialCallGraphBuilder(
+ appView, methodsToReprocess.build(appView, appView.graphLense()))
.build(executorService, timing);
return new PostMethodProcessor(appView, optimizationsMap, callGraph);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index e059db8..c0cdc54 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.desugar;
-import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.dex.Constants;
@@ -99,29 +98,25 @@
public static List<DexMethod> generateListOfBackportedMethods(
AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
- try {
- List<DexMethod> methods = new ArrayList<>();
- PrefixRewritingMapper rewritePrefix =
- options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
- AppInfo appInfo = null;
- if (androidApp != null) {
- DexApplication app =
- new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
- appInfo = new AppInfo(app);
- }
- AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
- BackportedMethodRewriter.RewritableMethods rewritableMethods =
- new BackportedMethodRewriter.RewritableMethods(options, appView);
- rewritableMethods.visit(methods::add);
- if (appInfo != null) {
- DesugaredLibraryRetargeter desugaredLibraryRetargeter =
- new DesugaredLibraryRetargeter(appView);
- desugaredLibraryRetargeter.visit(methods::add);
- }
- return methods;
- } catch (ExecutionException e) {
- throw unwrapExecutionException(e);
+ List<DexMethod> methods = new ArrayList<>();
+ PrefixRewritingMapper rewritePrefix =
+ options.desugaredLibraryConfiguration.createPrefixRewritingMapper(options);
+ AppInfo appInfo = null;
+ if (androidApp != null) {
+ DexApplication app =
+ new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
+ appInfo = new AppInfo(app);
}
+ AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
+ BackportedMethodRewriter.RewritableMethods rewritableMethods =
+ new BackportedMethodRewriter.RewritableMethods(options, appView);
+ rewritableMethods.visit(methods::add);
+ if (appInfo != null) {
+ DesugaredLibraryRetargeter desugaredLibraryRetargeter =
+ new DesugaredLibraryRetargeter(appView);
+ desugaredLibraryRetargeter.visit(methods::add);
+ }
+ return methods;
}
public static void registerAssumedLibraryTypes(InternalOptions options) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 7ed2773..f41bdce 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import com.google.common.base.Suppliers;
import com.google.common.primitives.Longs;
import java.nio.ByteBuffer;
@@ -723,6 +724,7 @@
forwardSourceCodeBuilder::build,
registry -> registry.registerInvokeDirect(implMethod)),
true);
+ accessorEncodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
implMethodHolder.addVirtualMethod(accessorEncodedMethod);
return new ProgramMethod(implMethodHolder, accessorEncodedMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index deb4579..45a8824 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -259,11 +260,12 @@
return descriptor == MATCH_FAILED ? null : descriptor;
}
- public static boolean isLambdaMetafactoryMethod(DexCallSite callSite, DexItemFactory factory) {
- if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
- return false;
- }
- return factory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+ public static boolean isLambdaMetafactoryMethod(
+ DexCallSite callSite, DexDefinitionSupplier definitions) {
+ return callSite.bootstrapMethod.type.isInvokeStatic()
+ && definitions
+ .dexItemFactory()
+ .isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
}
/**
@@ -272,7 +274,7 @@
*/
static LambdaDescriptor infer(
DexCallSite callSite, AppInfoWithClassHierarchy appInfo, ProgramMethod context) {
- if (!isLambdaMetafactoryMethod(callSite, appInfo.dexItemFactory())) {
+ if (!isLambdaMetafactoryMethod(callSite, appInfo)) {
return LambdaDescriptor.MATCH_FAILED;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java b/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
deleted file mode 100644
index cdb1699..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2019, 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.ir.optimize;
-
-import static com.google.common.base.Predicates.alwaysTrue;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.Assume.NoAssumption;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
-import java.util.ListIterator;
-import java.util.Set;
-import java.util.function.Predicate;
-
-public class AliasIntroducer implements Assumer {
- private final AppView<?> appView;
-
- public AliasIntroducer(AppView<?> appView) {
- this.appView = appView;
- }
-
- @Override
- public void insertAssumeInstructions(IRCode code, Timing timing) {
- insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
- }
-
- @Override
- public void insertAssumeInstructionsInBlocks(
- IRCode code,
- BasicBlockIterator blockIterator,
- Predicate<BasicBlock> blockTester,
- Timing timing) {
- while (blockIterator.hasNext()) {
- BasicBlock block = blockIterator.next();
- if (blockTester.test(block)) {
- insertAssumeNoneInstructionsInBlock(code, blockIterator, block);
- }
- }
- }
-
- private void insertAssumeNoneInstructionsInBlock(
- IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
- Set<Assume<NoAssumption>> deferredInstructions = Sets.newIdentityHashSet();
- InstructionListIterator instructionIterator = block.listIterator(code);
- while (instructionIterator.hasNext()) {
- Instruction current = instructionIterator.next();
- if (!current.hasOutValue() || !current.outValue().isUsed()) {
- continue;
- }
- Value outValue = current.outValue();
- // TODO(b/129859039): We may need similar concept when adding/testing assume-range
- if (outValue.getType().isPrimitiveType() || outValue.getType().isNullType()) {
- continue;
- }
- // Split block if needed
- BasicBlock insertionBlock =
- block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
- // Replace usages of out-value by the out-value of the AssumeNone instruction.
- Value aliasedValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
- outValue.replaceUsers(aliasedValue);
- // Insert AssumeNone instruction.
- Assume<NoAssumption> assumeNone =
- Assume.createAssumeNoneInstruction(aliasedValue, outValue, current, appView);
- // {@link BasicBlock} needs Argument instructions to be packed.
- if (current.isArgument()) {
- deferredInstructions.add(assumeNone);
- continue;
- }
- assumeNone.setPosition(
- appView.options().debug ? current.getPosition() : Position.none());
- if (insertionBlock == block) {
- instructionIterator.add(assumeNone);
- } else {
- insertionBlock.listIterator(code).add(assumeNone);
- }
- }
- // For deferred instructions due to packed arguments,
- // restart the iterator; move up to the last Argument instruction; and then add aliases.
- if (!deferredInstructions.isEmpty()) {
- final Position firstNonNonePosition = code.findFirstNonNonePosition();
- final InstructionListIterator it = block.listIterator(code);
- it.nextUntil(i -> !i.isArgument());
- it.previous();
- deferredInstructions.forEach(i -> {
- i.setPosition(firstNonNonePosition);
- it.add(i);
- });
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 8912aeb..d05d64e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -3,12 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo.abandoned;
+import static com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo.top;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -23,14 +27,19 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CodeOptimization;
import com.android.tools.r8.ir.conversion.PostOptimization;
-import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ForEachable;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
+import com.android.tools.r8.utils.LazyBox;
+import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
import java.util.Collection;
@@ -53,12 +62,14 @@
}
private final AppView<AppInfoWithLiveness> appView;
+ private final CallSiteOptimizationOptions options;
private ProgramMethodSet revisitedMethods = null;
private Mode mode = Mode.COLLECT;
public CallSiteOptimizationInfoPropagator(AppView<AppInfoWithLiveness> appView) {
assert appView.enableWholeProgramOptimizations();
this.appView = appView;
+ this.options = appView.options().callSiteOptimizationOptions();
if (Log.isLoggingEnabledFor(CallSiteOptimizationInfoPropagator.class)) {
revisitedMethods = ProgramMethodSet.create();
}
@@ -78,7 +89,7 @@
}
}
- public void collectCallSiteOptimizationInfo(IRCode code) {
+ public void collectCallSiteOptimizationInfo(IRCode code, Timing timing) {
// TODO(b/139246447): we could collect call site optimization during REVISIT mode as well,
// but that may require a separate copy of CallSiteOptimizationInfo.
if (mode != Mode.COLLECT) {
@@ -86,125 +97,204 @@
}
ProgramMethod context = code.context();
for (Instruction instruction : code.instructions()) {
- if (!instruction.isInvokeMethod() && !instruction.isInvokeCustom()) {
- continue;
- }
if (instruction.isInvokeMethod()) {
- InvokeMethod invoke = instruction.asInvokeMethod();
- if (invoke.isInvokeMethodWithDynamicDispatch()) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- ResolutionResult resolutionResult =
- appView.appInfo().resolveMethod(invokedMethod, invoke.isInvokeInterface());
- // For virtual and interface calls, proceed on valid results only (since it's enforced).
- if (!resolutionResult.isSingleResolution() || !resolutionResult.isVirtualTarget()) {
- continue;
- }
- // If the resolution ended up with a single target, check if it is a library override.
- // And if so, bail out early (to avoid expensive target lookup).
- ProgramMethod resolutionTarget =
- resolutionResult.asSingleResolution().getResolutionPair().asProgramMethod();
- if (resolutionTarget == null
- || isLibraryMethodOrLibraryMethodOverride(resolutionTarget)) {
- continue;
- }
- }
- ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
- assert invoke.isInvokeMethodWithDynamicDispatch()
- // For other invocation types, the size of targets should be at most one.
- || targets == null || targets.size() <= 1;
- if (targets == null || targets.isEmpty() || hasLibraryOverrides(targets)) {
- continue;
- }
- for (ProgramMethod target : targets) {
- recordArgumentsIfNecessary(target, invoke.inValues());
- }
- }
- if (instruction.isInvokeCustom()) {
- InvokeCustom invokeCustom = instruction.asInvokeCustom();
- // The bootstrap method for lambda allocation is always runtime internal.
- if (LambdaDescriptor.isLambdaMetafactoryMethod(
- invokeCustom.getCallSite(), appView.dexItemFactory())) {
- continue;
- }
- // In other cases, if the bootstrap method is program declared it will be called. The call
- // is with runtime provided arguments so ensure that the call-site info is TOP.
- DexMethodHandle bootstrapMethod = invokeCustom.getCallSite().bootstrapMethod;
- SingleResolutionResult resolution =
- appView
- .appInfo()
- .resolveMethod(
- bootstrapMethod.asMethod(),
- bootstrapMethod.isInterface)
- .asSingleResolution();
- if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
- resolution
- .getResolvedMethod()
- .joinCallSiteOptimizationInfo(CallSiteOptimizationInfo.TOP, appView);
- }
+ collectCallSiteOptimizationInfoForInvokeMethod(
+ instruction.asInvokeMethod(), context, timing);
+ } else if (instruction.isInvokeCustom()) {
+ collectCallSiteOptimizationInfoForInvokeCustom(instruction.asInvokeCustom());
}
}
}
- // TODO(b/140204899): Instead of reprocessing here, pass stopping criteria to lookup?
- // If any of target method is a library method override, bail out entirely/early.
- private boolean hasLibraryOverrides(ProgramMethodSet targets) {
- for (ProgramMethod target : targets) {
- if (isLibraryMethodOrLibraryMethodOverride(target)) {
- return true;
- }
+ private void collectCallSiteOptimizationInfoForInvokeMethod(
+ InvokeMethod invoke, ProgramMethod context, Timing timing) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ SingleResolutionResult resolutionResult =
+ appView
+ .appInfo()
+ .resolveMethod(invokedMethod, invoke.isInvokeInterface())
+ .asSingleResolution();
+ if (resolutionResult == null) {
+ return;
}
- return false;
+ // For virtual and interface calls, proceed on valid results only (since it's enforced).
+ if (invoke.isInvokeMethodWithDynamicDispatch() && !resolutionResult.isVirtualTarget()) {
+ return;
+ }
+ ProgramMethod resolutionTarget =
+ resolutionResult.asSingleResolution().getResolutionPair().asProgramMethod();
+ if (resolutionTarget == null || isMaybeClasspathOrLibraryMethodOverride(resolutionTarget)) {
+ return;
+ }
+ propagateArgumentsToDispatchTargets(invoke, resolutionResult, context, timing);
}
- private boolean isLibraryMethodOrLibraryMethodOverride(ProgramMethod target) {
+ private void collectCallSiteOptimizationInfoForInvokeCustom(InvokeCustom invoke) {
+ // If the bootstrap method is program declared it will be called. The call is with runtime
+ // provided arguments so ensure that the call-site info is TOP.
+ DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+ SingleResolutionResult resolution =
+ appView
+ .appInfo()
+ .resolveMethod(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
+ .asSingleResolution();
+ if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+ resolution.getResolvedMethod().joinCallSiteOptimizationInfo(top(), appView);
+ }
+ }
+
+ private boolean isMaybeClasspathOrLibraryMethodOverride(ProgramMethod target) {
// If the method overrides a library method, it is unsure how the method would be invoked by
// that library.
- return target.getDefinition().isLibraryMethodOverride().isTrue();
+ return target.getDefinition().isLibraryMethodOverride().isPossiblyTrue();
}
- // Record arguments for the given method if necessary.
- // At the same time, if it decides to bail out, make the corresponding info immutable so that we
- // can avoid recording arguments for the same method accidentally.
- private void recordArgumentsIfNecessary(ProgramMethod target, List<Value> inValues) {
- assert !target.getDefinition().isObsolete();
- if (appView.appInfo().neverReprocess.contains(target.getReference())) {
+ // Propagate information about the arguments to all possible dispatch targets of the invoke.
+ private void propagateArgumentsToDispatchTargets(
+ InvokeMethod invoke,
+ SingleResolutionResult resolutionResult,
+ ProgramMethod context,
+ Timing timing) {
+ if (invoke.arguments().isEmpty()) {
+ // Nothing to propagate.
return;
}
- if (target.getDefinition().getCallSiteOptimizationInfo().isTop()) {
+
+ if (invoke.arguments().size()
+ != invoke.getInvokedMethod().getArity()
+ + BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver())) {
+ // Verification error.
+ assert false;
return;
}
+
+ if (resolutionResult.getResolvedMethod().getCallSiteOptimizationInfo().isAbandoned()) {
+ // We stopped tracking the arguments to all possible dispatch targets.
+ assert verifyAllProgramDispatchTargetsHaveBeenAbandoned(invoke, context);
+ return;
+ }
+
+ timing.begin("Lookup possible dispatch targets");
+ ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
+ timing.end();
+
+ assert invoke.isInvokeMethodWithDynamicDispatch()
+ // For other invocation types, the size of targets should be at most one.
+ || targets == null
+ || targets.size() <= 1;
+
+ if (targets == null || targets.isEmpty()) {
+ return;
+ }
+
+ if (targets.size() > options.getMaxNumberOfDispatchTargetsBeforeAbandoning()) {
+ // If the number of targets exceed the threshold, abandon call site optimization for all
+ // targets.
+ abandonCallSitePropagation(invoke, resolutionResult, targets, context);
+ return;
+ }
+
+ timing.begin("Record arguments");
+ // Lazily computed piece of information that needs to be propagated to all dispatch targets.
+ LazyBox<CallSiteOptimizationInfo> callSiteOptimizationInfo =
+ new LazyBox<>(() -> computeCallSiteOptimizationInfoFromArguments(invoke, context, timing));
+ for (ProgramMethod target : targets) {
+ CallSiteOptimizationInfo newCallSiteOptimizationInfo =
+ propagateArgumentsToDispatchTarget(target, callSiteOptimizationInfo, timing);
+
+ // If one of the targets is abandoned or ends up being abandoned, then abandon call site
+ // optimization for all targets.
+ if (newCallSiteOptimizationInfo.isAbandoned()) {
+ abandonCallSitePropagation(invoke, resolutionResult, targets, context);
+ break;
+ }
+ }
+ timing.end();
+ }
+
+ private CallSiteOptimizationInfo propagateArgumentsToDispatchTarget(
+ ProgramMethod target,
+ LazyBox<CallSiteOptimizationInfo> lazyCallSiteOptimizationInfo,
+ Timing timing) {
+ CallSiteOptimizationInfo existingCallSiteOptimizationInfo =
+ target.getDefinition().getCallSiteOptimizationInfo();
+ if (existingCallSiteOptimizationInfo.isAbandoned()
+ || existingCallSiteOptimizationInfo.isTop()) {
+ return existingCallSiteOptimizationInfo;
+ }
+ if (!appView.appInfo().mayPropagateArgumentsTo(target)) {
+ return top();
+ }
+ timing.begin("Join argument info");
+ CallSiteOptimizationInfo callSiteOptimizationInfo =
+ lazyCallSiteOptimizationInfo.computeIfAbsent();
target
.getDefinition()
.joinCallSiteOptimizationInfo(
- computeCallSiteOptimizationInfoFromArguments(target, inValues), appView);
+ callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, target)
+ ? callSiteOptimizationInfo
+ : top(),
+ appView);
+ timing.end();
+ return target.getDefinition().getCallSiteOptimizationInfo();
+ }
+
+ private void abandonCallSitePropagation(
+ InvokeMethod invoke,
+ SingleResolutionResult resolutionResult,
+ ProgramMethodSet targets,
+ ProgramMethod context) {
+ if (invoke.isInvokeMethodWithDynamicDispatch()) {
+ // When there is a dynamic dispatch, we may have used dynamic type information to reduce the
+ // set of possible dispatch targets. However, it is an invariant that a method is marked as
+ // abandoned if-and-only-if that method and all of its overrides have been marked as
+ // abandoned. Therefore, we need to find all the overrides of the targeted method and mark
+ // them as abandoned, which we accomplish by performing a lookup without any dynamic type
+ // information.
+ InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
+ if (invokeMethodWithReceiver.hasRefinedReceiverUpperBoundType(appView)
+ || invokeMethodWithReceiver.hasRefinedReceiverLowerBoundType(appView)) {
+ LookupResult lookupResult =
+ resolutionResult.lookupVirtualDispatchTargets(context.getHolder(), appView.appInfo());
+ // This should always succeed since we already looked up `targets` successfully.
+ assert lookupResult.isLookupResultSuccess();
+ abandonCallSitePropagation(
+ consumer ->
+ lookupResult.forEach(
+ methodTarget -> {
+ if (methodTarget.isProgramMethod()) {
+ consumer.accept(methodTarget.asProgramMethod());
+ } else {
+ // This may happen if an interface method in the program is implemented
+ // by a method in the classpath or library.
+ assert invoke.isInvokeInterface();
+ }
+ },
+ emptyConsumer()));
+ return;
+ }
+ }
+ abandonCallSitePropagation(targets::forEach);
+ }
+
+ private void abandonCallSitePropagation(ForEachable<ProgramMethod> methods) {
+ methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
}
private CallSiteOptimizationInfo computeCallSiteOptimizationInfoFromArguments(
- ProgramMethod target, List<Value> inValues) {
- // No method body or no argument at all.
- if (target.getDefinition().shouldNotHaveCode() || inValues.size() == 0) {
- return CallSiteOptimizationInfo.TOP;
+ InvokeMethod invoke, ProgramMethod context, Timing timing) {
+ timing.begin("Compute argument info");
+ CallSiteOptimizationInfo callSiteOptimizationInfo =
+ ConcreteCallSiteOptimizationInfo.fromArguments(
+ appView, invoke.getInvokedMethod(), invoke.arguments(), context);
+ if (callSiteOptimizationInfo.isTop()) {
+ // If we are propagating unknown information to all call sites, then mark them as abandoned
+ // such that we bail out before looking up the possible dispatch targets if we see any future
+ // invokes to these methods.
+ callSiteOptimizationInfo = abandoned();
}
- // If pinned, that method could be invoked via reflection.
- if (appView.appInfo().isPinned(target.getReference())) {
- return CallSiteOptimizationInfo.TOP;
- }
- // If the method overrides a library method, it is unsure how the method would be invoked by
- // that library.
- if (target.getDefinition().isLibraryMethodOverride().isTrue()) {
- // But, should not be reachable, since we already bail out.
- assert false
- : "Trying to compute call site optimization info for " + target.toSourceString();
- return CallSiteOptimizationInfo.TOP;
- }
- // If the program already has illegal accesses, method resolution results will reflect that too.
- // We should avoid recording arguments in that case. E.g., b/139823850: static methods can be a
- // result of virtual call targets, if that's the only method that matches name and signature.
- int argumentOffset = target.getDefinition().isStatic() ? 0 : 1;
- if (inValues.size() != argumentOffset + target.getReference().getArity()) {
- return CallSiteOptimizationInfo.BOTTOM;
- }
- return ConcreteCallSiteOptimizationInfo.fromArguments(appView, target, inValues);
+ timing.end();
+ return callSiteOptimizationInfo;
}
// If collected call site optimization info has something useful, e.g., non-null argument,
@@ -236,7 +326,7 @@
int argIndex = argumentsSeen - 1;
AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(argIndex);
if (abstractValue.isSingleValue()) {
- assert appView.options().enablePropagationOfConstantsAtCallSites;
+ assert options.isConstantPropagationEnabled();
SingleValue singleValue = abstractValue.asSingleValue();
if (singleValue.isMaterializableInContext(appView, code.context())) {
Instruction replacement =
@@ -346,4 +436,15 @@
// Run IRConverter#optimize.
return null;
}
+
+ private boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
+ InvokeMethod invoke, ProgramMethod context) {
+ ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
+ if (targets != null) {
+ for (ProgramMethod target : targets) {
+ assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned();
+ }
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index a7a24cc..e769dc9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -376,7 +376,7 @@
// Array stores do not impact our ability to move constants into the class definition,
// as long as the instructions do not throw.
ArrayPut arrayPut = instruction.asArrayPut();
- if (arrayPut.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ if (arrayPut.instructionInstanceCanThrow(appView, context)) {
return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
}
} else if (instruction.isStaticGet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 22a211b..52844be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -7,12 +7,12 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
@@ -1387,12 +1387,18 @@
Value inValue = checkCast.object();
Value outValue = checkCast.outValue();
DexType castType = checkCast.getType();
+ DexType baseCastType = castType.toBaseType(dexItemFactory);
// If the cast type is not accessible in the current context, we should not remove the cast
- // in order to preserve IllegalAccessError. Note that JVM and ART behave differently: see
+ // in order to preserve runtime errors. Note that JVM and ART behave differently: see
// {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
- if (!isTypeVisibleFromContext(appView, code.context(), castType)) {
- return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+ if (baseCastType.isClassType()) {
+ DexClass baseCastClass = appView.definitionFor(baseCastType);
+ if (baseCastClass == null
+ || AccessControl.isClassAccessible(baseCastClass, code.context(), appView)
+ .isPossiblyFalse()) {
+ return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
+ }
}
// If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
@@ -1446,10 +1452,17 @@
// Returns true if the given instance-of instruction was removed.
private boolean removeInstanceOfInstructionIfTrivial(
InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
+ ProgramMethod context = code.context();
+
// If the instance-of type is not accessible in the current context, we should not remove the
// instance-of instruction in order to preserve IllegalAccessError.
- if (!isTypeVisibleFromContext(appView, code.context(), instanceOf.type())) {
- return false;
+ DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory);
+ if (instanceOfBaseType.isClassType()) {
+ DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType);
+ if (instanceOfClass == null
+ || AccessControl.isClassAccessible(instanceOfClass, context, appView).isPossiblyFalse()) {
+ return false;
+ }
}
Value inValue = instanceOf.value();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index b1af390..abf12f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -3,13 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isMemberVisibleFromOriginalContext;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -19,6 +18,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.Hash.Strategy;
@@ -132,20 +132,35 @@
// Give up in D8
continue;
}
+
assert appView.appInfo().hasLiveness();
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
+
+ SingleResolutionResult resolutionResult =
+ appInfoWithLiveness
+ .resolveMethod(invoke.getInvokedMethod(), invoke.isInvokeInterface())
+ .asSingleResolution();
+ if (resolutionResult == null
+ || resolutionResult
+ .isAccessibleFrom(context, appInfoWithLiveness)
+ .isPossiblyFalse()) {
+ continue;
+ }
+
// Check if the call has a single target; that target is side effect free; and
// that target's output depends only on arguments.
- DexEncodedMethod target = invoke.lookupSingleTarget(appView.withLiveness(), context);
+ // TODO(b/156853206): This should either (i) use the resolution result from above, (ii)
+ // return the resolution result such that the call site can perform the accessibility
+ // check, or (iii) always perform the accessibility check such that it can be skipped
+ // at the call site.
+ DexEncodedMethod target = invoke.lookupSingleTarget(appViewWithLiveness, context);
if (target == null
|| target.getOptimizationInfo().mayHaveSideEffects()
|| !target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) {
continue;
}
- // Verify that the target method is accessible in the current context.
- if (!isMemberVisibleFromOriginalContext(
- appView, context, target.holder(), target.accessFlags)) {
- continue;
- }
+
// Check if the call could throw a NPE as a result of the receiver being null.
if (current.isInvokeMethodWithReceiver()) {
Value receiver = current.asInvokeMethodWithReceiver().getReceiver().getAliasedValue();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index c17efb4..fe6cbb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -1143,8 +1143,7 @@
boolean skip =
!(options.enableDynamicTypeOptimization
|| options.enableNonNullTracking
- || options.enableValuePropagation
- || options.testing.forceAssumeNoneInsertion);
+ || options.enableValuePropagation);
if (skip) {
return;
}
@@ -1159,12 +1158,6 @@
applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
}
- // Introduce aliases only to the inlinee blocks.
- if (options.testing.forceAssumeNoneInsertion) {
- applyAssumerToInlinee(
- new AliasIntroducer(appView), code, blockIterator, block, inlineeBlocks, timing);
- }
-
// Add non-null IRs only to the inlinee blocks.
if (options.enableNonNullTracking) {
Assumer nonNullTracker = new NonNullTracker(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 59f5772..e08aeae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -11,7 +10,6 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Assume;
@@ -26,38 +24,36 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.LazyDominatorTree;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TriConsumer;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntListIterator;
+import java.util.ArrayList;
import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
-import java.util.function.Consumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
import java.util.function.Predicate;
public class NonNullTracker implements Assumer {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final Consumer<BasicBlock> splitBlockConsumer;
public NonNullTracker(AppView<? extends AppInfoWithClassHierarchy> appView) {
- this(appView, null);
- }
-
- public NonNullTracker(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- Consumer<BasicBlock> splitBlockConsumer) {
this.appView = appView;
- this.splitBlockConsumer = splitBlockConsumer;
}
@Override
@@ -81,277 +77,357 @@
BasicBlockIterator blockIterator,
Predicate<BasicBlock> blockTester,
Timing timing) {
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- Set<Value> knownToBeNonNullValues = Sets.newIdentityHashSet();
+ timing.begin("Part 1: Compute non null values");
+ NonNullValues nonNullValues = computeNonNullValues(code, blockIterator, blockTester);
+ timing.end();
+ if (nonNullValues.isEmpty()) {
+ return;
+ }
+
+ timing.begin("Part 2: Remove redundant assume instructions");
+ removeRedundantAssumeInstructions(nonNullValues);
+ timing.end();
+
+ timing.begin("Part 3: Compute dominated users");
+ Map<Instruction, Set<Value>> redundantKeys =
+ computeDominanceForNonNullValues(code, nonNullValues);
+ timing.end();
+ if (nonNullValues.isEmpty()) {
+ return;
+ }
+
+ timing.begin("Part 4: Remove redundant dominated assume instructions");
+ removeRedundantDominatedAssumeInstructions(nonNullValues, redundantKeys);
+ timing.end();
+ if (nonNullValues.isEmpty()) {
+ return;
+ }
+
+ timing.begin("Part 5: Materialize assume instructions");
+ materializeAssumeInstructions(code, nonNullValues);
+ timing.end();
+ }
+
+ private NonNullValues computeNonNullValues(
+ IRCode code, BasicBlockIterator blockIterator, Predicate<BasicBlock> blockTester) {
+ NonNullValues.Builder nonNullValuesBuilder = new NonNullValues.Builder();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
- if (!blockTester.test(block)) {
- continue;
+ if (blockTester.test(block)) {
+ computeNonNullValuesInBlock(code, blockIterator, block, nonNullValuesBuilder);
}
- // Add non-null after
- // 1) instructions that implicitly indicate receiver/array is not null.
- // 2) invocations that are guaranteed to return a non-null value.
- // 3) parameters that are not null after the invocation.
- // 4) field-get instructions that are guaranteed to read a non-null value.
- InstructionListIterator iterator = block.listIterator(code);
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- Value outValue = current.outValue();
+ }
+ return nonNullValuesBuilder.build();
+ }
- // Case (1), instructions that implicitly indicate receiver/array is not null.
- if (current.throwsOnNullInput()) {
- Value couldBeNonNull = current.getNonNullInput();
- if (isNullableReferenceTypeWithUsers(couldBeNonNull)) {
- knownToBeNonNullValues.add(couldBeNonNull);
- }
- }
+ private void computeNonNullValuesInBlock(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ BasicBlock block,
+ NonNullValues.Builder nonNullValuesBuilder) {
+ // Add non-null after
+ // 1) instructions that implicitly indicate receiver/array is not null.
+ // 2) invocations that are guaranteed to return a non-null value.
+ // 3) parameters that are not null after the invocation.
+ // 4) field-get instructions that are guaranteed to read a non-null value.
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction current = instructionIterator.next();
+ boolean needsAssumeInstruction = false;
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
-
- DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
- if (singleTarget != null) {
- MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
-
- // Case (2), invocations that are guaranteed to return a non-null value.
- if (optimizationInfo.neverReturnsNull()) {
- if (invoke.hasOutValue() && isNullableReferenceTypeWithUsers(outValue)) {
- knownToBeNonNullValues.add(outValue);
- }
- }
-
- // Case (3), parameters that are not null after the invocation.
- BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
- if (nonNullParamOnNormalExits != null) {
- for (int i = 0; i < current.inValues().size(); i++) {
- if (nonNullParamOnNormalExits.get(i)) {
- Value knownToBeNonNullValue = current.inValues().get(i);
- if (isNullableReferenceTypeWithUsers(knownToBeNonNullValue)) {
- knownToBeNonNullValues.add(knownToBeNonNullValue);
- }
- }
- }
- }
- }
- } else if (current.isFieldGet()) {
- // Case (4), field-get instructions that are guaranteed to read a non-null value.
- FieldInstruction fieldInstruction = current.asFieldInstruction();
- DexField field = fieldInstruction.getField();
- if (field.type.isReferenceType() && isNullableReferenceTypeWithUsers(outValue)) {
- DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
- if (encodedField != null) {
- FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
- if (optimizationInfo.getDynamicUpperBoundType() != null
- && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
- knownToBeNonNullValues.add(outValue);
- }
- }
- }
- }
-
- // This is to ensure that we do not add redundant non-null instructions.
- // Otherwise, we will have something like:
- // y <- assume-not-null(x)
- // ...
- // z <- assume-not-null(y)
- assert knownToBeNonNullValues.stream()
- .allMatch(NonNullTracker::isNullableReferenceTypeWithUsers);
-
- if (!knownToBeNonNullValues.isEmpty()) {
- addNonNullForValues(
- code,
- blockIterator,
- block,
- iterator,
- current,
- knownToBeNonNullValues,
- affectedValues);
- knownToBeNonNullValues.clear();
+ // Case (1), instructions that implicitly indicate receiver/array is not null.
+ if (current.throwsOnNullInput()) {
+ Value inValue = current.getNonNullInput();
+ if (nonNullValuesBuilder.isMaybeNull(inValue)
+ && isNullableReferenceTypeWithOtherNonDebugUsers(inValue, current)) {
+ nonNullValuesBuilder.addNonNullValueWithUnknownDominance(current, inValue);
+ needsAssumeInstruction = true;
}
}
- // Add non-null on top of the successor block if the current block ends with a null check.
- if (block.exit().isIf() && block.exit().asIf().isZeroTest()) {
- // if v EQ blockX
- // ... (fallthrough)
- // blockX: ...
- //
- // ~>
- //
- // if v EQ blockX
- // non_null_value <- non-null(v)
- // ...
- // blockX: ...
- //
- // or
- //
- // if v NE blockY
- // ...
- // blockY: ...
- //
- // ~>
- //
- // blockY: non_null_value <- non-null(v)
- // ...
- If theIf = block.exit().asIf();
- Value knownToBeNonNullValue = theIf.inValues().get(0);
- // Avoid adding redundant non-null instruction.
- if (isNullableReferenceTypeWithUsers(knownToBeNonNullValue)) {
- BasicBlock target = theIf.targetFromNonNullObject();
- // Ignore uncommon empty blocks.
- if (!target.isEmpty()) {
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- // Make sure there are no paths to the target block without passing the current block.
- if (dominatorTree.dominatedBy(target, block)) {
- // Collect users of the original value that are dominated by the target block.
- Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
- Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
- Set<BasicBlock> dominatedBlocks =
- Sets.newHashSet(dominatorTree.dominatedBlocks(target));
- for (Instruction user : knownToBeNonNullValue.uniqueUsers()) {
- if (dominatedBlocks.contains(user.getBlock())) {
- dominatedUsers.add(user);
- }
- }
- for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
- IntList dominatedPredecessorIndexes = findDominatedPredecessorIndexesInPhi(
- user, knownToBeNonNullValue, dominatedBlocks);
- if (!dominatedPredecessorIndexes.isEmpty()) {
- dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
- }
- }
- // Avoid adding a non-null for the value without meaningful users.
- if (knownToBeNonNullValue.isArgument()
- || !dominatedUsers.isEmpty()
- || !dominatedPhiUsersWithPositions.isEmpty()) {
- TypeElement typeLattice = knownToBeNonNullValue.getType();
- Value nonNullValue =
- code.createValue(
- typeLattice.asReferenceType().asMeetWithNotNull(),
- knownToBeNonNullValue.getLocalInfo());
- affectedValues.addAll(knownToBeNonNullValue.affectedValues());
- Assume<NonNullAssumption> nonNull =
- Assume.createAssumeNonNullInstruction(
- nonNullValue, knownToBeNonNullValue, theIf, appView);
- InstructionListIterator targetIterator = target.listIterator(code);
- nonNull.setPosition(targetIterator.next().getPosition());
- targetIterator.previous();
- targetIterator.add(nonNull);
- knownToBeNonNullValue.replaceSelectiveUsers(
- nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
- }
+ Value outValue = current.outValue();
+ if (current.isInvokeMethod()) {
+ InvokeMethod invoke = current.asInvokeMethod();
+ if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) {
+ // Case (2) and (3).
+ needsAssumeInstruction |=
+ computeNonNullValuesFromSingleTarget(code, invoke, nonNullValuesBuilder);
+ }
+ } else if (current.isFieldGet()) {
+ // Case (4), field-get instructions that are guaranteed to read a non-null value.
+ FieldInstruction fieldInstruction = current.asFieldInstruction();
+ DexField field = fieldInstruction.getField();
+ if (isNullableReferenceTypeWithNonDebugUsers(outValue)) {
+ DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
+ if (encodedField != null) {
+ FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
+ if (optimizationInfo.getDynamicUpperBoundType() != null
+ && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
+ nonNullValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
+ needsAssumeInstruction = true;
}
}
}
}
+
+ // If we need to insert an assume instruction into a block with catch handlers, we split the
+ // block such that the IR is ready for the insertion of the assume instruction.
+ //
+ // This splitting could in principle be deferred until we materialize the assume instructions,
+ // but then we would need to rewind the basic block iterator to the beginning, and scan over
+ // the instructions another time, splitting the blocks as needed.
+ if (block.hasCatchHandlers()) {
+ if (needsAssumeInstruction) {
+ BasicBlock insertionBlock = instructionIterator.split(code, blockIterator);
+ assert !instructionIterator.hasNext();
+ assert instructionIterator.peekPrevious().isGoto();
+ assert blockIterator.peekPrevious() == insertionBlock;
+ computeNonNullValuesInBlock(code, blockIterator, insertionBlock, nonNullValuesBuilder);
+ return;
+ }
+ if (current.instructionTypeCanThrow()) {
+ break;
+ }
+ }
+ }
+
+ If ifInstruction = block.exit().asIf();
+ if (ifInstruction != null && ifInstruction.isNonTrivialNullTest()) {
+ Value lhs = ifInstruction.lhs();
+ if (nonNullValuesBuilder.isMaybeNull(lhs)
+ && isNullableReferenceTypeWithOtherNonDebugUsers(lhs, ifInstruction)
+ && ifInstruction.targetFromNonNullObject().getPredecessors().size() == 1) {
+ nonNullValuesBuilder.addNonNullValueWithUnknownDominance(ifInstruction, lhs);
+ }
+ }
+ }
+
+ private boolean computeNonNullValuesFromSingleTarget(
+ IRCode code, InvokeMethod invoke, NonNullValues.Builder nonNullValuesBuilder) {
+ DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+ if (singleTarget == null) {
+ return false;
+ }
+
+ boolean needsAssumeInstruction = false;
+ MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+
+ // Case (2), invocations that are guaranteed to return a non-null value.
+ Value outValue = invoke.outValue();
+ if (outValue != null
+ && optimizationInfo.neverReturnsNull()
+ && isNullableReferenceTypeWithNonDebugUsers(outValue)) {
+ nonNullValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
+ needsAssumeInstruction = true;
+ }
+
+ // Case (3), parameters that are not null after the invocation.
+ BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
+ if (nonNullParamOnNormalExits != null) {
+ int start = invoke.isInvokeMethodWithReceiver() ? 1 : 0;
+ for (int i = start; i < invoke.arguments().size(); i++) {
+ if (nonNullParamOnNormalExits.get(i)) {
+ Value argument = invoke.getArgument(i);
+ if (nonNullValuesBuilder.isMaybeNull(argument)
+ && isNullableReferenceTypeWithOtherNonDebugUsers(argument, invoke)) {
+ nonNullValuesBuilder.addNonNullValueWithUnknownDominance(invoke, argument);
+ needsAssumeInstruction = true;
+ }
+ }
+ }
}
+ return needsAssumeInstruction;
+ }
+
+ private void removeRedundantAssumeInstructions(NonNullValues nonNullValues) {
+ nonNullValues.removeIf(
+ (instruction, nonNullValue) -> {
+ if (nonNullValue.isPhi()) {
+ return false;
+ }
+ Instruction definition = nonNullValue.definition;
+ return definition != instruction && nonNullValues.contains(definition, nonNullValue);
+ });
+ }
+
+ private Map<Instruction, Set<Value>> computeDominanceForNonNullValues(
+ IRCode code, NonNullValues nonNullValues) {
+ Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>();
+ LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code);
+ Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>();
+ nonNullValues.computeDominance(
+ (instruction, nonNullValue) -> {
+ Set<Value> alreadyNonNullValues = redundantKeys.get(instruction);
+ if (alreadyNonNullValues != null && alreadyNonNullValues.contains(nonNullValue)) {
+ // Returning redundant() will cause the entry (instruction, nonNullValue) to be removed.
+ return NonNullDominance.redundant();
+ }
+
+ // If this value is non-null since its definition, then it is known to dominate all users.
+ if (nonNullValue == instruction.outValue()) {
+ return NonNullDominance.everything();
+ }
+
+ // If we learn that this value is known to be non-null in the same block as it is defined,
+ // and it is not used between its definition and the instruction that performs the null
+ // check, then the non-null-value is known to dominate all other users than the null check
+ // itself.
+ BasicBlock block = instruction.getBlock();
+ if (nonNullValue.getBlock() == block
+ && block.exit().isGoto()
+ && !instruction.getBlock().hasCatchHandlers()) {
+ InstructionIterator iterator = instruction.getBlock().iterator();
+ if (!nonNullValue.isPhi()) {
+ iterator.nextUntil(x -> x != nonNullValue.definition);
+ iterator.previous();
+ }
+ boolean isUsedBeforeInstruction = false;
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current == instruction) {
+ break;
+ }
+ if (current.inValues().contains(nonNullValue)
+ || current.getDebugValues().contains(nonNullValue)) {
+ isUsedBeforeInstruction = true;
+ break;
+ }
+ }
+ if (!isUsedBeforeInstruction) {
+ return NonNullDominance.everythingElse();
+ }
+ }
+
+ // Otherwise, we need a dominator tree to determine which users are dominated.
+ BasicBlock insertionBlock = getInsertionBlock(instruction);
+
+ assert nonNullValue.hasPhiUsers()
+ || nonNullValue.uniqueUsers().stream().anyMatch(user -> user != instruction)
+ || nonNullValue.isArgument();
+
+ // Find all users of the original value that are dominated by either the current block
+ // or the new split-off block. Since NPE can be explicitly caught, nullness should be
+ // propagated through dominance.
+ DominatorTree dominatorTree = lazyDominatorTree.get();
+ Set<BasicBlock> dominatedBlocks =
+ dominatedBlocksCache.computeIfAbsent(
+ insertionBlock, x -> dominatorTree.dominatedBlocks(x, Sets.newIdentityHashSet()));
+
+ NonNullDominance.Builder dominance = NonNullDominance.builder(nonNullValue);
+ for (Instruction user : nonNullValue.uniqueUsers()) {
+ if (user != instruction && dominatedBlocks.contains(user.getBlock())) {
+ if (user.getBlock() == insertionBlock && insertionBlock == block) {
+ Instruction first = block.iterator().nextUntil(x -> x == instruction || x == user);
+ assert first != null;
+ if (first == user) {
+ continue;
+ }
+ }
+ dominance.addDominatedUser(user);
+
+ // Record that there is no need to insert an assume instruction for the non-null-value
+ // after the given user in case the user is also a null check for the non-null-value.
+ redundantKeys
+ .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet())
+ .add(nonNullValue);
+ }
+ }
+ for (Phi user : nonNullValue.uniquePhiUsers()) {
+ IntList dominatedPredecessorIndices =
+ findDominatedPredecessorIndexesInPhi(user, nonNullValue, dominatedBlocks);
+ if (!dominatedPredecessorIndices.isEmpty()) {
+ dominance.addDominatedPhiUser(user, dominatedPredecessorIndices);
+ }
+ }
+ return dominance.build();
+ });
+ return redundantKeys;
+ }
+
+ private void removeRedundantDominatedAssumeInstructions(
+ NonNullValues nonNullValues, Map<Instruction, Set<Value>> redundantKeys) {
+ nonNullValues.removeAll(redundantKeys);
+ }
+
+ private void materializeAssumeInstructions(IRCode code, NonNullValues nonNullValues) {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions =
+ new IdentityHashMap<>();
+ nonNullValues.forEach(
+ (instruction, nonNullValue, dominance) -> {
+ BasicBlock block = instruction.getBlock();
+ BasicBlock insertionBlock = getInsertionBlock(instruction);
+
+ Value newValue =
+ code.createValue(
+ nonNullValue.getType().asReferenceType().asMeetWithNotNull(),
+ nonNullValue.getLocalInfo());
+ if (dominance.isEverything()) {
+ nonNullValue.replaceUsers(newValue);
+ } else if (dominance.isEverythingElse()) {
+ nonNullValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
+ nonNullValue.replacePhiUsers(newValue);
+ } else if (dominance.isSomething()) {
+ SomethingNonNullDominance somethingDominance = dominance.asSomething();
+ somethingDominance
+ .getDominatedPhiUsers()
+ .forEach(
+ (user, indices) -> {
+ IntListIterator iterator = indices.iterator();
+ while (iterator.hasNext()) {
+ Value operand = user.getOperand(iterator.nextInt());
+ if (operand != nonNullValue) {
+ assert operand.isDefinedByInstructionSatisfying(
+ Instruction::isAssumeNonNull);
+ iterator.remove();
+ }
+ }
+ });
+ nonNullValue.replaceSelectiveUsers(
+ newValue,
+ somethingDominance.getDominatedUsers(),
+ somethingDominance.getDominatedPhiUsers());
+ }
+ affectedValues.addAll(newValue.affectedValues());
+
+ Assume<NonNullAssumption> assumeInstruction =
+ Assume.createAssumeNonNullInstruction(newValue, nonNullValue, instruction, appView);
+ assumeInstruction.setPosition(instruction.getPosition());
+ if (insertionBlock != block) {
+ insertionBlock.listIterator(code).add(assumeInstruction);
+ } else {
+ pendingInsertions
+ .computeIfAbsent(block, ignore -> new IdentityHashMap<>())
+ .computeIfAbsent(instruction, ignore -> new ArrayList<>())
+ .add(assumeInstruction);
+ }
+ });
+ pendingInsertions.forEach(
+ (block, pendingInsertionsPerInstruction) -> {
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext() && !pendingInsertionsPerInstruction.isEmpty()) {
+ Instruction instruction = instructionIterator.next();
+ List<Instruction> pendingAssumeInstructions =
+ pendingInsertionsPerInstruction.remove(instruction);
+ if (pendingAssumeInstructions != null) {
+ pendingAssumeInstructions.forEach(instructionIterator::add);
+ }
+ }
+ });
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
- private void addNonNullForValues(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- BasicBlock block,
- InstructionListIterator iterator,
- Instruction current,
- Set<Value> knownToBeNonNullValues,
- Set<Value> affectedValues) {
- // First, if the current block has catch handler, split into two blocks, e.g.,
- //
- // ...x
- // invoke(rcv, ...)
- // ...y
- //
- // ~>
- //
- // ...x
- // invoke(rcv, ...)
- // goto A
- //
- // A: ...y // blockWithNonNullInstruction
- boolean split = block.hasCatchHandlers();
- BasicBlock blockWithNonNullInstruction;
- if (split) {
- blockWithNonNullInstruction = iterator.split(code, blockIterator);
- if (splitBlockConsumer != null) {
- splitBlockConsumer.accept(blockWithNonNullInstruction);
- }
- } else {
- blockWithNonNullInstruction = block;
+ private BasicBlock getInsertionBlock(Instruction instruction) {
+ if (instruction.isIf()) {
+ return instruction.asIf().targetFromNonNullObject();
}
-
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- for (Value knownToBeNonNullValue : knownToBeNonNullValues) {
- // Find all users of the original value that are dominated by either the current block
- // or the new split-off block. Since NPE can be explicitly caught, nullness should be
- // propagated through dominance.
- Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
- Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
- Map<Phi, IntList> dominatedPhiUsersWithPositions = new IdentityHashMap<>();
- Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
- for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
- dominatedBlocks.add(dominatee);
- InstructionIterator dominateeIterator = dominatee.iterator();
- if (dominatee == blockWithNonNullInstruction && !split) {
- // In the block where the non null instruction will be inserted, skip instructions up to
- // and including the insertion point.
- dominateeIterator.nextUntil(instruction -> instruction == current);
- }
- while (dominateeIterator.hasNext()) {
- Instruction potentialUser = dominateeIterator.next();
- if (users.contains(potentialUser)) {
- dominatedUsers.add(potentialUser);
- }
- }
- }
- for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
- IntList dominatedPredecessorIndexes =
- findDominatedPredecessorIndexesInPhi(user, knownToBeNonNullValue, dominatedBlocks);
- if (!dominatedPredecessorIndexes.isEmpty()) {
- dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
- }
- }
-
- // Only insert non-null instruction if it is ever used.
- // Exception: if it is an argument, non-null IR can be used to compute non-null parameter.
- if (knownToBeNonNullValue.isArgument()
- || !dominatedUsers.isEmpty()
- || !dominatedPhiUsersWithPositions.isEmpty()) {
- // Add non-null fake IR, e.g.,
- // ...x
- // invoke(rcv, ...)
- // goto A
- // ...
- // A: non_null_rcv <- non-null(rcv)
- // ...y
- TypeElement typeLattice = knownToBeNonNullValue.getType();
- assert typeLattice.isReferenceType();
- Value nonNullValue =
- code.createValue(
- typeLattice.asReferenceType().asMeetWithNotNull(),
- knownToBeNonNullValue.getLocalInfo());
- affectedValues.addAll(knownToBeNonNullValue.affectedValues());
- Assume<NonNullAssumption> nonNull =
- Assume.createAssumeNonNullInstruction(
- nonNullValue, knownToBeNonNullValue, current, appView);
- nonNull.setPosition(current.getPosition());
- if (blockWithNonNullInstruction != block) {
- // If we split, add non-null IR on top of the new split block.
- blockWithNonNullInstruction.listIterator(code).add(nonNull);
- } else {
- // Otherwise, just add it to the current block at the position of the iterator.
- iterator.add(nonNull);
- }
-
- // Replace all users of the original value that are dominated by either the current block
- // or the new split-off block.
- knownToBeNonNullValue.replaceSelectiveUsers(
- nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
- }
+ BasicBlock block = instruction.getBlock();
+ if (block.hasCatchHandlers()) {
+ return block.exit().asGoto().getTarget();
}
+ return block;
}
private IntList findDominatedPredecessorIndexesInPhi(
@@ -379,10 +455,346 @@
return predecessorIndexes;
}
- private static boolean isNullableReferenceTypeWithUsers(Value value) {
+ private static boolean isNullableReferenceType(Value value) {
TypeElement type = value.getType();
- return type.isReferenceType()
- && type.asReferenceType().isNullable()
- && value.numberOfAllUsers() > 0;
+ return type.isReferenceType() && type.asReferenceType().isNullable();
+ }
+
+ private static boolean isNullableReferenceTypeWithNonDebugUsers(Value value) {
+ return isNullableReferenceType(value) && value.numberOfAllNonDebugUsers() > 0;
+ }
+
+ private static boolean isNullableReferenceTypeWithOtherNonDebugUsers(
+ Value value, Instruction ignore) {
+ if (isNullableReferenceType(value)) {
+ if (value.hasPhiUsers()) {
+ return true;
+ }
+ for (Instruction user : value.uniqueUsers()) {
+ if (user != ignore) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static class NonNullValues {
+
+ /**
+ * A mapping from each instruction to the (in and out) values that are guaranteed to be non-null
+ * by the instruction. Each non-null value is subsequently mapped to the set of users that it
+ * dominates.
+ */
+ Map<Instruction, Map<Value, NonNullDominance>> nonNullValues;
+
+ public NonNullValues(Map<Instruction, Map<Value, NonNullDominance>> nonNullValues) {
+ this.nonNullValues = nonNullValues;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ void computeDominance(BiFunction<Instruction, Value, NonNullDominance> function) {
+ Iterator<Entry<Instruction, Map<Value, NonNullDominance>>> outerIterator =
+ nonNullValues.entrySet().iterator();
+ while (outerIterator.hasNext()) {
+ Entry<Instruction, Map<Value, NonNullDominance>> outerEntry = outerIterator.next();
+ Instruction instruction = outerEntry.getKey();
+ Map<Value, NonNullDominance> dominancePerValue = outerEntry.getValue();
+ Iterator<Entry<Value, NonNullDominance>> innerIterator =
+ dominancePerValue.entrySet().iterator();
+ while (innerIterator.hasNext()) {
+ Entry<Value, NonNullDominance> innerEntry = innerIterator.next();
+ Value nonNullValue = innerEntry.getKey();
+ NonNullDominance dominance = innerEntry.getValue();
+ if (dominance.isEverything()) {
+ assert nonNullValue.isDefinedByInstructionSatisfying(
+ definition -> definition.outValue() == nonNullValue);
+ continue;
+ }
+ assert dominance.isUnknown();
+ dominance = function.apply(instruction, nonNullValue);
+ if ((dominance.isNothing() && !nonNullValue.isArgument()) || dominance.isUnknown()) {
+ innerIterator.remove();
+ } else {
+ innerEntry.setValue(dominance);
+ }
+ }
+ if (dominancePerValue.isEmpty()) {
+ outerIterator.remove();
+ }
+ }
+ }
+
+ boolean contains(Instruction instruction, Value nonNullValue) {
+ Map<Value, NonNullDominance> dominancePerValue = nonNullValues.get(instruction);
+ return dominancePerValue != null && dominancePerValue.containsKey(nonNullValue);
+ }
+
+ boolean isEmpty() {
+ return nonNullValues.isEmpty();
+ }
+
+ void forEach(TriConsumer<Instruction, Value, NonNullDominance> consumer) {
+ nonNullValues.forEach(
+ (instruction, dominancePerValue) ->
+ dominancePerValue.forEach(
+ (nonNullValue, dominance) ->
+ consumer.accept(instruction, nonNullValue, dominance)));
+ }
+
+ void removeAll(Map<Instruction, Set<Value>> keys) {
+ keys.forEach(
+ (instruction, values) -> {
+ Map<Value, NonNullDominance> dominancePerValue = nonNullValues.get(instruction);
+ if (dominancePerValue != null) {
+ values.forEach(dominancePerValue::remove);
+ if (dominancePerValue.isEmpty()) {
+ nonNullValues.remove(instruction);
+ }
+ }
+ });
+ }
+
+ void removeIf(BiPredicate<Instruction, Value> predicate) {
+ Iterator<Entry<Instruction, Map<Value, NonNullDominance>>> outerIterator =
+ nonNullValues.entrySet().iterator();
+ while (outerIterator.hasNext()) {
+ Entry<Instruction, Map<Value, NonNullDominance>> outerEntry = outerIterator.next();
+ Instruction instruction = outerEntry.getKey();
+ Map<Value, NonNullDominance> dominancePerValue = outerEntry.getValue();
+ Iterator<Entry<Value, NonNullDominance>> innerIterator =
+ dominancePerValue.entrySet().iterator();
+ while (innerIterator.hasNext()) {
+ Value nonNullValue = innerIterator.next().getKey();
+ if (predicate.test(instruction, nonNullValue)) {
+ innerIterator.remove();
+ }
+ }
+ if (dominancePerValue.isEmpty()) {
+ outerIterator.remove();
+ }
+ }
+ }
+
+ static class Builder {
+
+ private final Map<Instruction, Map<Value, NonNullDominance>> nonNullValues =
+ new LinkedHashMap<>();
+
+ // Used to avoid unnecessary block splitting during phase 1.
+ private final Set<Value> nonNullValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
+
+ private void add(Instruction instruction, Value nonNullValue, NonNullDominance dominance) {
+ nonNullValues
+ .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
+ .put(nonNullValue, dominance);
+ if (dominance.isEverything()) {
+ nonNullValuesKnownToDominateAllUsers.add(nonNullValue);
+ }
+ }
+
+ void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) {
+ add(instruction, nonNullValue, NonNullDominance.everything());
+ }
+
+ void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) {
+ add(instruction, nonNullValue, NonNullDominance.unknown());
+ }
+
+ public boolean isMaybeNull(Value value) {
+ return !nonNullValuesKnownToDominateAllUsers.contains(value);
+ }
+
+ public NonNullValues build() {
+ return new NonNullValues(nonNullValues);
+ }
+ }
+ }
+
+ abstract static class NonNullDominance {
+
+ boolean isEverything() {
+ return false;
+ }
+
+ boolean isEverythingElse() {
+ return false;
+ }
+
+ boolean isNothing() {
+ return false;
+ }
+
+ boolean isSomething() {
+ return false;
+ }
+
+ SomethingNonNullDominance asSomething() {
+ return null;
+ }
+
+ boolean isUnknown() {
+ return false;
+ }
+
+ public static Builder builder(Value nonNullValue) {
+ return new Builder(nonNullValue);
+ }
+
+ public static EverythingNonNullDominance everything() {
+ return EverythingNonNullDominance.getInstance();
+ }
+
+ public static EverythingElseNonNullDominance everythingElse() {
+ return EverythingElseNonNullDominance.getInstance();
+ }
+
+ public static NothingNonNullDominance nothing() {
+ return NothingNonNullDominance.getInstance();
+ }
+
+ public static UnknownNonNullDominance redundant() {
+ return unknown();
+ }
+
+ public static SomethingNonNullDominance something(
+ Set<Instruction> dominatedUsers, Map<Phi, IntList> dominatedPhiUsers) {
+ return new SomethingNonNullDominance(dominatedUsers, dominatedPhiUsers);
+ }
+
+ public static UnknownNonNullDominance unknown() {
+ return UnknownNonNullDominance.getInstance();
+ }
+
+ static class Builder {
+
+ private final Value nonNullValue;
+
+ private final Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+ private final Map<Phi, IntList> dominatedPhiUsers = new IdentityHashMap<>();
+
+ private Builder(Value nonNullValue) {
+ this.nonNullValue = nonNullValue;
+ }
+
+ void addDominatedUser(Instruction user) {
+ assert nonNullValue.uniqueUsers().contains(user);
+ assert !dominatedUsers.contains(user);
+ dominatedUsers.add(user);
+ }
+
+ void addDominatedPhiUser(Phi user, IntList dominatedPredecessorIndices) {
+ assert nonNullValue.uniquePhiUsers().contains(user);
+ assert !dominatedPhiUsers.containsKey(user);
+ dominatedPhiUsers.put(user, dominatedPredecessorIndices);
+ }
+
+ NonNullDominance build() {
+ if (dominatedUsers.isEmpty() && dominatedPhiUsers.isEmpty()) {
+ return nothing();
+ }
+ assert dominatedUsers.size() < nonNullValue.uniqueUsers().size()
+ || dominatedPhiUsers.size() < nonNullValue.uniquePhiUsers().size();
+ return something(dominatedUsers, dominatedPhiUsers);
+ }
+ }
+ }
+
+ static class EverythingNonNullDominance extends NonNullDominance {
+
+ private static final EverythingNonNullDominance INSTANCE = new EverythingNonNullDominance();
+
+ private EverythingNonNullDominance() {}
+
+ public static EverythingNonNullDominance getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ boolean isEverything() {
+ return true;
+ }
+ }
+
+ static class EverythingElseNonNullDominance extends NonNullDominance {
+
+ private static final EverythingElseNonNullDominance INSTANCE =
+ new EverythingElseNonNullDominance();
+
+ private EverythingElseNonNullDominance() {}
+
+ public static EverythingElseNonNullDominance getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ boolean isEverythingElse() {
+ return true;
+ }
+ }
+
+ static class NothingNonNullDominance extends NonNullDominance {
+
+ private static final NothingNonNullDominance INSTANCE = new NothingNonNullDominance();
+
+ private NothingNonNullDominance() {}
+
+ public static NothingNonNullDominance getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ boolean isNothing() {
+ return true;
+ }
+ }
+
+ static class SomethingNonNullDominance extends NonNullDominance {
+
+ private final Set<Instruction> dominatedUsers;
+ private final Map<Phi, IntList> dominatedPhiUsers;
+
+ SomethingNonNullDominance(
+ Set<Instruction> dominatedUsers, Map<Phi, IntList> dominatedPhiUsers) {
+ this.dominatedUsers = dominatedUsers;
+ this.dominatedPhiUsers = dominatedPhiUsers;
+ }
+
+ public Set<Instruction> getDominatedUsers() {
+ return dominatedUsers;
+ }
+
+ public Map<Phi, IntList> getDominatedPhiUsers() {
+ return dominatedPhiUsers;
+ }
+
+ @Override
+ boolean isSomething() {
+ return true;
+ }
+
+ @Override
+ SomethingNonNullDominance asSomething() {
+ return this;
+ }
+ }
+
+ static class UnknownNonNullDominance extends NonNullDominance {
+
+ private static final UnknownNonNullDominance INSTANCE = new UnknownNonNullDominance();
+
+ private UnknownNonNullDominance() {}
+
+ public static UnknownNonNullDominance getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ boolean isUnknown() {
+ return true;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 5c4197c..0ac7f7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -114,8 +114,8 @@
/** Result of first step (see {@link Outliner#createOutlineMethodIdentifierGenerator()}. */
private final List<Multiset<Wrapper<ProgramMethod>>> candidateMethodLists = new ArrayList<>();
/** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
- private final LongLivedProgramMethodSetBuilder methodsSelectedForOutlining =
- new LongLivedProgramMethodSetBuilder();
+ private final LongLivedProgramMethodSetBuilder<?> methodsSelectedForOutlining =
+ LongLivedProgramMethodSetBuilder.create();
/** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
private final Map<Outline, List<ProgramMethod>> outlineSites = new HashMap<>();
/** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 32bea32..6077882 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -23,13 +23,13 @@
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.AbstractError;
import com.android.tools.r8.ir.analysis.TypeChecker;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
@@ -374,7 +374,6 @@
if (instruction.isFieldInstruction()) {
rewriteFieldInstruction(
instruction.asFieldInstruction(),
- blockIterator,
instructionIterator,
code,
assumeDynamicTypeRemover,
@@ -411,13 +410,11 @@
}
if (current.isFieldInstruction()) {
// Other resolution-related errors come first.
- AbstractError abstractError =
- current.asFieldInstruction().instructionInstanceCanThrow(appView, context);
- if (abstractError.isThrowing()
- && abstractError.getSpecificError(appView.dexItemFactory())
- != appView.dexItemFactory().npeType) {
- // We can't replace the current instruction with `throw null` if it may throw another
- // Error/Exception than NullPointerException.
+ FieldInstruction fieldInstruction = current.asFieldInstruction();
+ // We can't replace the current instruction with `throw null` if it may throw another
+ // exception than NullPointerException.
+ if (fieldInstruction.instructionInstanceCanThrow(
+ appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
return false;
}
}
@@ -446,7 +443,6 @@
// At this point, field-instruction whose target field type is uninstantiated will be handled.
private void rewriteFieldInstruction(
FieldInstruction instruction,
- ListIterator<BasicBlock> blockIterator,
InstructionListIterator instructionIterator,
IRCode code,
AssumeDynamicTypeRemover assumeDynamicTypeRemover,
@@ -461,8 +457,7 @@
return;
}
- boolean instructionCanBeRemoved =
- instruction.instructionInstanceCanThrow(appView, context).isThrowing();
+ boolean instructionCanBeRemoved = !instruction.instructionInstanceCanThrow(appView, context);
BasicBlock block = instruction.getBlock();
if (instruction.isFieldPut()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index b601000..c179446 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -7,7 +7,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClass.FieldSetter;
import com.android.tools.r8.graph.DexEncodedField;
@@ -17,9 +17,11 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
@@ -37,7 +39,9 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Opcodes;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CodeOptimization;
@@ -48,7 +52,6 @@
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.Reporter;
@@ -58,6 +61,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.IdentityHashMap;
@@ -148,26 +152,23 @@
}
}
}
- if (instruction.isConstClass()) {
- analyzeConstClass(instruction.asConstClass(), eligibleEnums);
- } else if (instruction.isCheckCast()) {
- analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
- } else if (instruction.isInvokeStatic()) {
- // TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
- // valueOf static methods only if such methods are unused, such methods cannot be
- // called. the long term solution is to simply move called methods to a companion class,
- // as any static helper method, and remove these checks.
- DexMethod invokedMethod = instruction.asInvokeStatic().getInvokedMethod();
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
- if (enumClass != null) {
- if (factory.enumMethods.isValueOfMethod(invokedMethod, enumClass)) {
- markEnumAsUnboxable(Reason.VALUE_OF_INVOKE, enumClass);
- } else if (factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
- markEnumAsUnboxable(Reason.VALUES_INVOKE, enumClass);
- } else {
- assert false; // We do not allow any other static call in unboxing candidates.
- }
- }
+ switch (instruction.opcode()) {
+ case Opcodes.CONST_CLASS:
+ analyzeConstClass(instruction.asConstClass(), eligibleEnums);
+ break;
+ case Opcodes.CHECK_CAST:
+ analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
+ break;
+ case Opcodes.INVOKE_STATIC:
+ analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums);
+ break;
+ case Opcodes.STATIC_GET:
+ case Opcodes.INSTANCE_GET:
+ case Opcodes.STATIC_PUT:
+ case Opcodes.INSTANCE_PUT:
+ analyzeFieldInstruction(instruction.asFieldInstruction(), code);
+ break;
+ default: // Nothing to do for other instructions.
}
}
for (Phi phi : block.getPhis()) {
@@ -195,6 +196,26 @@
}
}
+ private void analyzeFieldInstruction(FieldInstruction fieldInstruction, IRCode code) {
+ DexField field = fieldInstruction.getField();
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(field.holder);
+ if (enumClass != null) {
+ FieldResolutionResult resolutionResult =
+ appView.appInfo().resolveField(field, code.context());
+ if (resolutionResult.isFailedOrUnknownResolution()) {
+ markEnumAsUnboxable(Reason.UNRESOLVABLE_FIELD, enumClass);
+ }
+ }
+ }
+
+ private void analyzeInvokeStatic(InvokeStatic invokeStatic, Set<DexType> eligibleEnums) {
+ DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+ DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+ if (enumClass != null) {
+ eligibleEnums.add(enumClass.type);
+ }
+ }
+
private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
// We are doing a type check, which typically means the in-value is of an upper
// type and cannot be dealt with.
@@ -297,58 +318,54 @@
public void unboxEnums(
PostMethodProcessor.Builder postBuilder,
ExecutorService executorService,
- OptimizationFeedbackDelayed feedback,
- ClassStaticizer classStaticizer)
+ OptimizationFeedbackDelayed feedback)
throws ExecutionException {
// At this point the enumsToUnbox are no longer candidates, they will all be unboxed.
if (enumsUnboxingCandidates.isEmpty()) {
return;
}
ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
- NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
+ NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
- if (enumUnboxingLens != null) {
- appView.setGraphLense(enumUnboxingLens);
- appView.setAppInfo(
- appView
- .appInfo()
- .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
- // Update optimization info.
- feedback.fixupOptimizationInfos(
- appView,
- executorService,
- new OptimizationInfoFixer() {
- @Override
- public void fixup(DexEncodedField field) {
- FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
- if (optimizationInfo.isMutableFieldOptimizationInfo()) {
- optimizationInfo
- .asMutableFieldOptimizationInfo()
- .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
- .fixupAbstractValue(appView, appView.graphLense());
- } else {
- assert optimizationInfo.isDefaultFieldOptimizationInfo();
- }
+ GraphLense previousLens = appView.graphLense();
+ appView.setGraphLense(enumUnboxingLens);
+ appView.setAppInfo(
+ appView.appInfo().rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
+ // Update optimization info.
+ feedback.fixupOptimizationInfos(
+ appView,
+ executorService,
+ new OptimizationInfoFixer() {
+ @Override
+ public void fixup(DexEncodedField field) {
+ FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+ if (optimizationInfo.isMutableFieldOptimizationInfo()) {
+ optimizationInfo
+ .asMutableFieldOptimizationInfo()
+ .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
+ .fixupAbstractValue(appView, appView.graphLense());
+ } else {
+ assert optimizationInfo.isDefaultFieldOptimizationInfo();
}
+ }
- @Override
- public void fixup(DexEncodedMethod method) {
- MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
- if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
- optimizationInfo
- .asUpdatableMethodOptimizationInfo()
- .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
- .fixupAbstractReturnValue(appView, appView.graphLense())
- .fixupInstanceInitializerInfo(appView, appView.graphLense());
- } else {
- assert optimizationInfo.isDefaultMethodOptimizationInfo();
- }
+ @Override
+ public void fixup(DexEncodedMethod method) {
+ MethodOptimizationInfo optimizationInfo = method.getOptimizationInfo();
+ if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
+ optimizationInfo
+ .asUpdatableMethodOptimizationInfo()
+ .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
+ .fixupAbstractReturnValue(appView, appView.graphLense())
+ .fixupInstanceInitializerInfo(appView, appView.graphLense());
+ } else {
+ assert optimizationInfo.isDefaultMethodOptimizationInfo();
}
- });
- }
+ }
+ });
postBuilder.put(this);
- postBuilder.mapDexEncodedMethods(appView);
+ postBuilder.rewrittenWithLens(appView, previousLens);
}
public void finishAnalysis() {
@@ -600,12 +617,11 @@
return Sets.newIdentityHashSet();
}
- public void synthesizeUtilityClass(
- DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+ public void synthesizeUtilityMethods(
+ Builder<?> builder, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
if (enumUnboxerRewriter != null) {
- enumUnboxerRewriter.synthesizeEnumUnboxingUtilityClass(
- appBuilder, converter, executorService);
+ enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(builder, converter, executorService);
}
}
@@ -634,6 +650,7 @@
INSTANCE_FIELD,
GENERIC_INVOKE,
UNEXPECTED_STATIC_FIELD,
+ UNRESOLVABLE_FIELD,
VIRTUAL_METHOD,
UNEXPECTED_DIRECT_METHOD,
CONST_CLASS,
@@ -663,6 +680,7 @@
private class TreeFixer {
+ private final List<DexEncodedMethod> unboxedEnumsMethods = new ArrayList<>();
private final EnumUnboxingLens.Builder lensBuilder = EnumUnboxingLens.builder();
private final Set<DexType> enumsToUnbox;
@@ -671,27 +689,29 @@
}
private NestedGraphLense fixupTypeReferences() {
+ assert enumUnboxerRewriter != null;
// Fix all methods and fields using enums to unbox.
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (enumsToUnbox.contains(clazz.type)) {
assert clazz.instanceFields().size() == 0;
- // TODO(b/150370354): Remove when static methods are supported.
- if (appView.options().testing.enumUnboxingRewriteJavaCGeneratedMethod) {
- // Clear only the initializers.
- clazz
- .methods()
- .forEach(
- m -> {
- if (m.isInitializer()) {
- clearEnumToUnboxMethod(m);
- }
- });
- clazz.getMethodCollection().replaceMethods(this::fixupMethod);
- } else {
- clazz.methods().forEach(this::clearEnumToUnboxMethod);
- }
+ // Clear the initializers and move the static methods to the utility class.
+ Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
+ clazz
+ .methods()
+ .forEach(
+ m -> {
+ if (m.isInitializer()) {
+ clearEnumToUnboxMethod(m);
+ } else {
+ assert m.isStatic();
+ unboxedEnumsMethods.add(
+ fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
+ methodsToRemove.add(m);
+ }
+ });
+ clazz.getMethodCollection().removeMethods(methodsToRemove);
} else {
- clazz.getMethodCollection().replaceMethods(this::fixupMethod);
+ clazz.getMethodCollection().replaceMethods(this::fixupEncodedMethod);
fixupFields(clazz.staticFields(), clazz::setStaticField);
fixupFields(clazz.instanceFields(), clazz::setInstanceField);
}
@@ -699,6 +719,10 @@
for (DexType toUnbox : enumsToUnbox) {
lensBuilder.map(toUnbox, factory.intType);
}
+ DexProgramClass utilityClass =
+ appView.definitionForProgramType(factory.enumUnboxingUtilityType);
+ assert utilityClass != null : "Should have been synthesized upfront";
+ utilityClass.addDirectMethods(unboxedEnumsMethods);
return lensBuilder.build(factory, appView.graphLense());
}
@@ -715,7 +739,19 @@
appView);
}
- private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
+ private DexEncodedMethod fixupEncodedMethodToUtility(
+ DexEncodedMethod encodedMethod, DexType newHolder) {
+ DexMethod method = encodedMethod.method;
+ DexString newMethodName =
+ factory.createString(
+ enumUnboxerRewriter.compatibleName(method.holder) + "$$" + method.name.toString());
+ DexMethod newMethod = fixupMethod(method, newHolder, newMethodName);
+ lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+ encodedMethod.accessFlags.promoteToPublic();
+ return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ }
+
+ private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
DexMethod newMethod = fixupMethod(encodedMethod.method);
if (newMethod != encodedMethod.method) {
lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic());
@@ -747,7 +783,11 @@
}
private DexMethod fixupMethod(DexMethod method) {
- return factory.createMethod(method.holder, fixupProto(method.proto), method.name);
+ return fixupMethod(method, method.holder, method.name);
+ }
+
+ private DexMethod fixupMethod(DexMethod method, DexType newHolder, DexString newMethodName) {
+ return factory.createMethod(newHolder, fixupProto(method.proto), newMethodName);
}
private DexProto fixupProto(DexProto proto) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 7ae1ffa..b5530c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -81,23 +81,11 @@
enumUnboxer.reportFailure(clazz.type, Reason.MISSING_INFO_MAP);
return false;
}
- // Methods values, valueOf, init, clinit are present on each enum.
- // Methods init and clinit are required if the enum is used.
- // Methods valueOf and values are normally kept by the commonly used/recommended enum keep rule
- // -keepclassmembers,allowoptimization enum * {
- // public static **[] values();
- // public static ** valueOf(java.lang.String);
- // }
- // In general there will be 4 methods, unless the enum keep rule is not present.
- if (clazz.getMethodCollection().numberOfDirectMethods() > 4) {
- enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
- return false;
- }
+
+ // TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used.
+ // Only Enums with default initializers and static methods can be unboxed at the moment.
for (DexEncodedMethod directMethod : clazz.directMethods()) {
- if (!(factory.enumMethods.isValuesMethod(directMethod.method, clazz)
- || factory.enumMethods.isValueOfMethod(directMethod.method, clazz)
- || isStandardEnumInitializer(directMethod)
- || directMethod.isClassInitializer())) {
+ if (!(directMethod.isStatic() || isStandardEnumInitializer(directMethod))) {
enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 383b03b..6f6fba1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -45,7 +45,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -67,7 +66,6 @@
private final Map<DexMethod, DexEncodedMethod> extraUtilityMethods = new ConcurrentHashMap<>();
private final Map<DexField, DexEncodedField> extraUtilityFields = new ConcurrentHashMap<>();
- private final DexType utilityClassType;
private final DexMethod ordinalUtilityMethod;
private final DexMethod valuesUtilityMethod;
@@ -83,15 +81,14 @@
}
this.enumsToUnbox = builder.build();
- this.utilityClassType = factory.enumUnboxingUtilityType;
this.ordinalUtilityMethod =
factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_ORDINAL);
this.valuesUtilityMethod =
factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intArrayType, factory.intType),
ENUM_UNBOXING_UTILITY_VALUES);
}
@@ -222,13 +219,13 @@
return enumsToUnbox.containsEnum(type.asClassType().getClassType());
}
- private String compatibleName(DexType type) {
+ public String compatibleName(DexType type) {
return type.toSourceString().replace('.', '$');
}
private DexField createValuesField(DexType type) {
return factory.createField(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.intArrayType,
factory.enumValuesFieldName + "$field$" + compatibleName(type));
}
@@ -244,7 +241,7 @@
private DexMethod createValuesMethod(DexType type) {
return factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intArrayType),
factory.enumValuesFieldName + "$method$" + compatibleName(type));
}
@@ -253,7 +250,11 @@
DexMethod method, DexField fieldValues, int numEnumInstances) {
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
- appView, utilityClassType, fieldValues, numEnumInstances, valuesUtilityMethod)
+ appView,
+ factory.enumUnboxingUtilityType,
+ fieldValues,
+ numEnumInstances,
+ valuesUtilityMethod)
.generateCfCode();
return synthesizeUtilityMethod(cfCode, method, true);
}
@@ -262,7 +263,7 @@
assert enumsToUnbox.containsEnum(type);
DexMethod valueOf =
factory.createMethod(
- utilityClassType,
+ factory.enumUnboxingUtilityType,
factory.createProto(factory.intType, factory.stringType),
"valueOf" + compatibleName(type));
extraUtilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, type));
@@ -283,9 +284,8 @@
&& enumsToUnbox.containsEnum(baseType.asClassType().getClassType());
}
- // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
- void synthesizeEnumUnboxingUtilityClass(
- DexApplication.Builder<?> appBuilder, IRConverter converter, ExecutorService executorService)
+ void synthesizeEnumUnboxingUtilityMethods(
+ Builder<?> builder, IRConverter converter, ExecutorService executorService)
throws ExecutionException {
// Synthesize a class which holds various utility methods that may be called from the IR
// rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
@@ -302,38 +302,51 @@
if (requiredMethods.isEmpty()) {
return;
}
- DexEncodedField[] fields = extraUtilityFields.values().toArray(DexEncodedField.EMPTY_ARRAY);
- Arrays.sort(fields, (f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
+ List<DexEncodedField> fields = new ArrayList<>(extraUtilityFields.values());
+ fields.sort((f1, f2) -> f1.field.name.slowCompareTo(f2.field.name));
DexProgramClass utilityClass =
- new DexProgramClass(
- utilityClassType,
- null,
- new SynthesizedOrigin("EnumUnboxing ", getClass()),
- ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
- factory.objectType,
- DexTypeList.empty(),
- factory.createString("enumunboxing"),
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- DexAnnotationSet.empty(),
- fields,
- DexEncodedField.EMPTY_ARRAY,
- // All synthesized methods are static in this case.
- requiredMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
- DexEncodedMethod.EMPTY_ARRAY,
- factory.getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
- appBuilder.addSynthesizedClass(utilityClass, utilityClassInMainDexList());
- appView.appInfo().addSynthesizedClass(utilityClass);
+ appView.definitionForProgramType(factory.enumUnboxingUtilityType);
+ assert utilityClass != null : "Should have been synthesized upfront.";
+ utilityClass.appendStaticFields(fields);
+ utilityClass.addDirectMethods(requiredMethods);
+ assert requiredMethods.stream().allMatch(DexEncodedMethod::isPublic);
+ if (utilityClassInMainDexList()) {
+ builder.addToMainDexList(Collections.singletonList(utilityClass.type));
+ }
+ // TODO(b/147860220): Use processMethodsConcurrently on requiredMethods instead.
converter.optimizeSynthesizedClass(utilityClass, executorService);
}
+ public static DexProgramClass synthesizeEmptyEnumUnboxingUtilityClass(AppView<?> appView) {
+ DexItemFactory factory = appView.dexItemFactory();
+ return new DexProgramClass(
+ factory.enumUnboxingUtilityType,
+ null,
+ new SynthesizedOrigin("EnumUnboxing ", EnumUnboxingRewriter.class),
+ ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
+ factory.objectType,
+ DexTypeList.empty(),
+ factory.createString("enumunboxing"),
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ factory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ }
+
private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
CfCode cfCode =
new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
- appView, utilityClassType, enumType, enumsToUnbox.getEnumValueInfoMap(enumType))
+ appView,
+ factory.enumUnboxingUtilityType,
+ enumType,
+ enumsToUnbox.getEnumValueInfoMap(enumType))
.generateCfCode();
return new DexEncodedMethod(
method,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/AbandonedCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/AbandonedCallSiteOptimizationInfo.java
new file mode 100644
index 0000000..a1dece6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/AbandonedCallSiteOptimizationInfo.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, 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.ir.optimize.info;
+
+/**
+ * Used to represent that nothing is known about the argument values of a given method and all of
+ * its overrides.
+ */
+public class AbandonedCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
+
+ private static final AbandonedCallSiteOptimizationInfo INSTANCE =
+ new AbandonedCallSiteOptimizationInfo();
+
+ private AbandonedCallSiteOptimizationInfo() {}
+
+ static AbandonedCallSiteOptimizationInfo getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAbandoned() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
index 20ee5df..14d9dc0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
@@ -5,10 +5,16 @@
// Nothing is known about arguments at call sites.
public class BottomCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
- static BottomCallSiteOptimizationInfo INSTANCE = new BottomCallSiteOptimizationInfo();
+
+ private static final BottomCallSiteOptimizationInfo INSTANCE =
+ new BottomCallSiteOptimizationInfo();
private BottomCallSiteOptimizationInfo() {}
+ static BottomCallSiteOptimizationInfo getInstance() {
+ return INSTANCE;
+ }
+
@Override
public boolean isBottom() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index e85cf9e..ad274e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -13,8 +14,22 @@
// A flat lattice structure:
// BOTTOM, TOP, and a lattice element that holds accumulated argument info.
public abstract class CallSiteOptimizationInfo {
- public static BottomCallSiteOptimizationInfo BOTTOM = BottomCallSiteOptimizationInfo.INSTANCE;
- public static TopCallSiteOptimizationInfo TOP = TopCallSiteOptimizationInfo.INSTANCE;
+
+ public static AbandonedCallSiteOptimizationInfo abandoned() {
+ return AbandonedCallSiteOptimizationInfo.getInstance();
+ }
+
+ public static BottomCallSiteOptimizationInfo bottom() {
+ return BottomCallSiteOptimizationInfo.getInstance();
+ }
+
+ public static TopCallSiteOptimizationInfo top() {
+ return TopCallSiteOptimizationInfo.getInstance();
+ }
+
+ public boolean isAbandoned() {
+ return false;
+ }
public boolean isBottom() {
return false;
@@ -33,19 +48,19 @@
}
public CallSiteOptimizationInfo join(
- CallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
- if (isBottom()) {
+ CallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
+ if (isAbandoned() || other.isAbandoned()) {
+ return abandoned();
+ }
+ if (isBottom() || other.isTop()) {
return other;
}
- if (other.isBottom()) {
+ if (isTop() || other.isBottom()) {
return this;
}
- if (isTop() || other.isTop()) {
- return TOP;
- }
assert isConcreteCallSiteOptimizationInfo() && other.isConcreteCallSiteOptimizationInfo();
return asConcreteCallSiteOptimizationInfo()
- .join(other.asConcreteCallSiteOptimizationInfo(), appView, encodedMethod);
+ .join(other.asConcreteCallSiteOptimizationInfo(), appView, method);
}
/**
@@ -54,10 +69,14 @@
* if a certain argument is guaranteed to be definitely not null for all call sites, null-check on
* that argument can be simplified during the reprocessing of the method.
*/
- public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
+ public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
return false;
}
+ public final boolean hasUsefulOptimizationInfo(AppView<?> appView, ProgramMethod method) {
+ return hasUsefulOptimizationInfo(appView, method.getDefinition());
+ }
+
// The index exactly matches with in values of invocation, i.e., even including receiver.
public TypeElement getDynamicUpperBoundType(int argIndex) {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 86d107a..3d20843 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -30,12 +31,6 @@
private final Int2ReferenceMap<TypeElement> dynamicUpperBoundTypes;
private final Int2ReferenceMap<AbstractValue> constants;
- private ConcreteCallSiteOptimizationInfo(
- DexEncodedMethod encodedMethod, boolean allowConstantPropagation) {
- this(encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1),
- allowConstantPropagation);
- }
-
private ConcreteCallSiteOptimizationInfo(int size, boolean allowConstantPropagation) {
assert size > 0;
this.size = size;
@@ -44,12 +39,12 @@
}
CallSiteOptimizationInfo join(
- ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
+ ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod method) {
assert this.size == other.size;
- boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+ boolean allowConstantPropagation =
+ appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
ConcreteCallSiteOptimizationInfo result =
new ConcreteCallSiteOptimizationInfo(this.size, allowConstantPropagation);
- assert result.dynamicUpperBoundTypes != null;
for (int i = 0; i < result.size; i++) {
if (allowConstantPropagation) {
assert result.constants != null;
@@ -72,42 +67,40 @@
result.dynamicUpperBoundTypes.put(
i, thisUpperBoundType.join(otherUpperBoundType, appView));
}
- if (result.hasUsefulOptimizationInfo(appView, encodedMethod)) {
+ if (result.hasUsefulOptimizationInfo(appView, method)) {
return result;
}
// As soon as we know the argument collection so far does not have any useful optimization info,
// move to TOP so that further collection can be simply skipped.
- return TOP;
+ return top();
}
- private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod encodedMethod) {
- int argOffset = encodedMethod.isStatic() ? 0 : 1;
- int size = encodedMethod.method.getArity() + argOffset;
+ private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod method) {
+ int argOffset = method.isStatic() ? 0 : 1;
+ int size = method.method.getArity() + argOffset;
TypeElement[] staticTypes = new TypeElement[size];
- if (!encodedMethod.isStatic()) {
- staticTypes[0] =
- TypeElement.fromDexType(encodedMethod.holder(), definitelyNotNull(), appView);
+ if (!method.isStatic()) {
+ staticTypes[0] = TypeElement.fromDexType(method.holder(), definitelyNotNull(), appView);
}
- for (int i = 0; i < encodedMethod.method.getArity(); i++) {
+ for (int i = 0; i < method.method.getArity(); i++) {
staticTypes[i + argOffset] =
- TypeElement.fromDexType(
- encodedMethod.method.proto.parameters.values[i], maybeNull(), appView);
+ TypeElement.fromDexType(method.parameters().values[i], maybeNull(), appView);
}
return staticTypes;
}
@Override
- public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
- TypeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
+ public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod method) {
+ TypeElement[] staticTypes = getStaticTypes(appView, method);
for (int i = 0; i < size; i++) {
- ParameterUsage parameterUsage = encodedMethod.getOptimizationInfo().getParameterUsages(i);
+ ParameterUsage parameterUsage = method.getOptimizationInfo().getParameterUsages(i);
// If the parameter is not used, passing accurate argument info doesn't matter.
if (parameterUsage != null && parameterUsage.notUsed()) {
continue;
}
AbstractValue abstractValue = getAbstractArgumentValue(i);
if (abstractValue.isNonTrivial()) {
- assert appView.options().enablePropagationOfConstantsAtCallSites;
+ assert appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
return true;
}
@@ -118,7 +111,7 @@
if (dynamicUpperBoundType == null) {
continue;
}
- assert appView.options().enablePropagationOfDynamicTypesAtCallSites;
+ assert appView.options().callSiteOptimizationOptions().isTypePropagationEnabled();
// To avoid the full join of type lattices below, separately check if the nullability of
// arguments is improved, and if so, we can eagerly conclude that we've collected useful
// call site information for this method.
@@ -154,37 +147,50 @@
}
public static CallSiteOptimizationInfo fromArguments(
- AppView<AppInfoWithLiveness> appView, ProgramMethod target, List<Value> inValues) {
- boolean allowConstantPropagation = appView.options().enablePropagationOfConstantsAtCallSites;
+ AppView<AppInfoWithLiveness> appView,
+ DexMethod invokedMethod,
+ List<Value> arguments,
+ ProgramMethod context) {
+ boolean allowConstantPropagation =
+ appView.options().callSiteOptimizationOptions().isConstantPropagationEnabled();
ConcreteCallSiteOptimizationInfo newCallSiteInfo =
- new ConcreteCallSiteOptimizationInfo(target.getDefinition(), allowConstantPropagation);
- assert newCallSiteInfo.size == inValues.size();
+ new ConcreteCallSiteOptimizationInfo(arguments.size(), allowConstantPropagation);
+ boolean hasReceiver = arguments.size() > invokedMethod.getArity();
+ boolean isTop = true;
assert newCallSiteInfo.dynamicUpperBoundTypes != null;
for (int i = 0; i < newCallSiteInfo.size; i++) {
- Value arg = inValues.get(i);
+ Value arg = arguments.get(i);
+
+ // Constant propagation.
if (allowConstantPropagation) {
assert newCallSiteInfo.constants != null;
Value aliasedValue = arg.getAliasedValue();
if (!aliasedValue.isPhi()) {
- AbstractValue abstractValue = aliasedValue.definition.getAbstractValue(appView, target);
+ AbstractValue abstractValue = aliasedValue.definition.getAbstractValue(appView, context);
if (abstractValue.isNonTrivial()) {
newCallSiteInfo.constants.put(i, abstractValue);
+ isTop = false;
}
}
}
- if (arg.getType().isPrimitiveType()) {
- continue;
+ // Type propagation.
+ if (arg.getType().isReferenceType()) {
+ TypeElement staticType =
+ TypeElement.fromDexType(
+ hasReceiver ? invokedMethod.holder : invokedMethod.proto.getParameter(i),
+ maybeNull(),
+ appView);
+ TypeElement dynamicUpperBoundType = arg.getDynamicUpperBoundType(appView);
+ if (dynamicUpperBoundType != staticType) {
+ newCallSiteInfo.dynamicUpperBoundTypes.put(i, dynamicUpperBoundType);
+ isTop = false;
+ } else {
+ newCallSiteInfo.dynamicUpperBoundTypes.put(i, staticType);
+ }
}
- assert arg.getType().isReferenceType();
- newCallSiteInfo.dynamicUpperBoundTypes.put(i, arg.getDynamicUpperBoundType(appView));
}
- if (newCallSiteInfo.hasUsefulOptimizationInfo(appView, target.getDefinition())) {
- return newCallSiteInfo;
- }
- // As soon as we know the current call site does not have any useful optimization info,
- // return TOP so that further collection can be simply skipped.
- return TOP;
+ return isTop ? CallSiteOptimizationInfo.top() : newCallSiteInfo;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 8bb40e0..a1a6638 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -534,8 +534,7 @@
}
Value object =
instancePut.object().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
- if (object != receiver
- || instancePut.instructionInstanceCanThrow(appView, context).isThrowing()) {
+ if (object != receiver || instancePut.instructionInstanceCanThrow(appView, context)) {
builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 8942ea1..9696162 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -156,7 +156,7 @@
@Override
public void setBridgeInfo(DexEncodedMethod method, BridgeInfo bridgeInfo) {
- // Ignored.
+ method.getMutableOptimizationInfo().setBridgeInfo(bridgeInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
index 5e1fdd8..241b934 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
@@ -5,10 +5,15 @@
// Nothing should not be assumed about arguments at call sites.
class TopCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
- static TopCallSiteOptimizationInfo INSTANCE = new TopCallSiteOptimizationInfo();
+
+ private static final TopCallSiteOptimizationInfo INSTANCE = new TopCallSiteOptimizationInfo();
private TopCallSiteOptimizationInfo() {}
+ static TopCallSiteOptimizationInfo getInstance() {
+ return INSTANCE;
+ }
+
@Override
public boolean isTop() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
index 8f28fd5..9eeb579 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -166,7 +166,8 @@
}
// It must not return a value, or the return value must be the result value of the invoke.
return ret.isReturnVoid()
- || ret.returnValue() == (returnCast != null ? returnCast : invoke).outValue();
+ || ret.returnValue().getAliasedValue()
+ == (returnCast != null ? returnCast : invoke).outValue();
}
private static BridgeInfo failure() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
index c718391..2683d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeInfo.java
@@ -9,8 +9,6 @@
*/
public abstract class BridgeInfo {
- public abstract boolean hasSameTarget(BridgeInfo bridgeInfo);
-
public boolean isVirtualBridgeInfo() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
index 585699f..a07abdc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/VirtualBridgeInfo.java
@@ -38,13 +38,6 @@
}
@Override
- public boolean hasSameTarget(BridgeInfo bridgeInfo) {
- assert bridgeInfo.isVirtualBridgeInfo();
- VirtualBridgeInfo virtualBridgeInfo = bridgeInfo.asVirtualBridgeInfo();
- return invokedMethod.match(virtualBridgeInfo.invokedMethod);
- }
-
- @Override
public boolean isVirtualBridgeInfo() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 35add7f..6e0805e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -195,8 +195,8 @@
// we mark a method for further processing, and then invalidate the only lambda referenced
// from it. In this case we will reprocess method that does not need patching, but it
// should not be happening very frequently and we ignore possible overhead.
- private final LongLivedProgramMethodSetBuilder methodsToReprocess =
- new LongLivedProgramMethodSetBuilder();
+ private final LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> methodsToReprocess =
+ LongLivedProgramMethodSetBuilder.createSorted();
private final AppView<AppInfoWithLiveness> appView;
private final Kotlin kotlin;
@@ -455,8 +455,7 @@
if (methodsToReprocess.isEmpty()) {
return;
}
- SortedProgramMethodSet methods =
- methodsToReprocess.build(appView, ignore -> SortedProgramMethodSet.create());
+ SortedProgramMethodSet methods = methodsToReprocess.build(appView);
converter.processMethodsConcurrently(methods, executorService);
assert methods.stream()
.map(DexClassAndMethod::getDefinition)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index c8e08c1..049748f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -94,7 +94,7 @@
if (DexAnnotation.isSignatureAnnotation(annotation, kotlin.factory)) {
continue;
}
- if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+ if (annotation.annotation.type == kotlin.factory.kotlinMetadataType) {
continue;
}
return false;
@@ -111,7 +111,7 @@
continue;
}
- if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+ if (annotation.annotation.type == appView.dexItemFactory().kotlinMetadataType) {
// Ignore kotlin metadata on lambda classes. Metadata on synthetic
// classes exists but is not used in the current Kotlin version (1.2.21)
// and newly generated lambda _group_ class is not exactly a kotlin class.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index acba57f..499c975 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -102,7 +102,7 @@
}
}
- final Map<CandidateInfo, LongLivedProgramMethodSetBuilder> referencedFrom =
+ final Map<CandidateInfo, LongLivedProgramMethodSetBuilder<?>> referencedFrom =
new IdentityHashMap<>();
// The map storing all the potential candidates for staticizing.
@@ -291,7 +291,7 @@
// Ignore just read instruction.
}
referencedFrom
- .computeIfAbsent(candidateInfo, ignore -> new LongLivedProgramMethodSetBuilder())
+ .computeIfAbsent(candidateInfo, ignore -> LongLivedProgramMethodSetBuilder.create())
.add(context);
}
continue;
@@ -313,7 +313,7 @@
CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
if (info != null) {
referencedFrom
- .computeIfAbsent(info, ignore -> new LongLivedProgramMethodSetBuilder())
+ .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
.add(context);
// If the candidate is still valid, ignore all usages in further analysis.
Value value = instruction.outValue();
@@ -329,7 +329,7 @@
CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
if (info != null) {
referencedFrom
- .computeIfAbsent(info, ignore -> new LongLivedProgramMethodSetBuilder())
+ .computeIfAbsent(info, ignore -> LongLivedProgramMethodSetBuilder.create())
.add(context);
// If the candidate is still valid, ignore all usages in further analysis.
Value value = instruction.outValue();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
index 1ac680a..84d6e82 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -95,4 +95,10 @@
KmValueParameterVisitor get(int flags, String name);
}
+
+ @FunctionalInterface
+ public interface KmFlexibleUpperBoundVisitorProvider {
+
+ KmTypeVisitor get(int flags, String typeFlexibilityId);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index b225440..602165c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -161,7 +161,6 @@
}
public final class Metadata {
- public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
public final DexString kind = factory.createString("k");
public final DexString metadataVersion = factory.createString("mv");
public final DexString bytecodeVersion = factory.createString("bv");
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 2cb8fe4..b507093 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -23,6 +23,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmType;
@@ -45,6 +46,7 @@
// TODO(b/154347404): Understand enum entries.
private final List<String> enumEntries;
private final DexType anonymousObjectOrigin;
+ private final String packageName;
public KotlinClassInfo(
int flags,
@@ -57,7 +59,8 @@
List<DexType> sealedSubClasses,
List<DexType> nestedClasses,
List<String> enumEntries,
- DexType anonymousObjectOrigin) {
+ DexType anonymousObjectOrigin,
+ String packageName) {
this.flags = flags;
this.name = name;
this.moduleName = moduleName;
@@ -69,13 +72,16 @@
this.nestedClasses = nestedClasses;
this.enumEntries = enumEntries;
this.anonymousObjectOrigin = anonymousObjectOrigin;
+ this.packageName = packageName;
}
public static KotlinClassInfo create(
KmClass kmClass,
+ String packageName,
DexClass hostClass,
DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ Reporter reporter,
+ Consumer<DexEncodedMethod> keepByteCode) {
Map<String, DexEncodedField> fieldMap = new HashMap<>();
for (DexEncodedField field : hostClass.fields()) {
fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
@@ -101,7 +107,7 @@
}
KotlinDeclarationContainerInfo container =
KotlinDeclarationContainerInfo.create(
- kmClass, methodMap, fieldMap, definitionSupplier, reporter);
+ kmClass, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode);
setCompanionObject(kmClass, hostClass, reporter);
return new KotlinClassInfo(
kmClass.getFlags(),
@@ -114,7 +120,8 @@
getSealedSubClasses(hostClass, kmClass.getSealedSubclasses(), definitionSupplier),
getNestedClasses(hostClass, kmClass.getNestedClasses(), definitionSupplier),
kmClass.getEnumEntries(),
- getAnonymousObjectOrigin(kmClass, definitionSupplier));
+ getAnonymousObjectOrigin(kmClass, definitionSupplier),
+ packageName);
}
private static DexType getAnonymousObjectOrigin(
@@ -267,4 +274,9 @@
kmClass.accept(writer);
return writer.write().getHeader();
}
+
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index 76bfb00..f9662ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -54,4 +54,6 @@
KotlinClassHeader rewrite(
DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens);
+
+ String getPackageName();
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index a1ad70f..5fe02b2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -18,18 +19,34 @@
import com.android.tools.r8.utils.StringDiagnostic;
import java.util.IdentityHashMap;
import java.util.Map;
+import java.util.function.Consumer;
import kotlinx.metadata.InconsistentKotlinMetadataException;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinClassMetadataReader {
+ private static int KOTLIN_METADATA_KIND_LAMBDA = 3;
+
public static KotlinClassLevelInfo getKotlinInfo(
- Kotlin kotlin, DexClass clazz, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
- DexAnnotation meta = clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
+ Kotlin kotlin,
+ DexClass clazz,
+ DexDefinitionSupplier definitionSupplier,
+ Reporter reporter,
+ boolean onlyProcessLambda,
+ Consumer<DexEncodedMethod> keepByteCode) {
+ DexAnnotation meta =
+ clazz
+ .annotations()
+ .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType);
if (meta != null) {
try {
- return createKotlinInfo(kotlin, clazz, meta.annotation, definitionSupplier, reporter);
+ KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation);
+ if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
+ return NO_KOTLIN_INFO;
+ }
+ return createKotlinInfo(
+ kotlin, clazz, kMetadata, definitionSupplier, reporter, keepByteCode);
} catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
reporter.info(
new StringDiagnostic(
@@ -85,29 +102,45 @@
public static KotlinClassLevelInfo createKotlinInfo(
Kotlin kotlin,
DexClass clazz,
- DexEncodedAnnotation metadataAnnotation,
+ KotlinClassMetadata kMetadata,
DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
- KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
-
+ Reporter reporter,
+ Consumer<DexEncodedMethod> keepByteCode) {
+ String packageName = kMetadata.getHeader().getPackageName();
if (kMetadata instanceof KotlinClassMetadata.Class) {
return KotlinClassInfo.create(
- ((KotlinClassMetadata.Class) kMetadata).toKmClass(), clazz, definitionSupplier, reporter);
+ ((KotlinClassMetadata.Class) kMetadata).toKmClass(),
+ packageName,
+ clazz,
+ definitionSupplier,
+ reporter,
+ keepByteCode);
} else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
// e.g., B.kt becomes class `BKt`
return KotlinFileFacadeInfo.create(
- (KotlinClassMetadata.FileFacade) kMetadata, clazz, definitionSupplier, reporter);
+ (KotlinClassMetadata.FileFacade) kMetadata,
+ packageName,
+ clazz,
+ definitionSupplier,
+ reporter,
+ keepByteCode);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
// multi-file class with the same @JvmName.
return KotlinMultiFileClassFacadeInfo.create(
- (KotlinClassMetadata.MultiFileClassFacade) kMetadata, definitionSupplier);
+ (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, definitionSupplier);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
// A single file, which is part of multi-file class.
return KotlinMultiFileClassPartInfo.create(
- (KotlinClassMetadata.MultiFileClassPart) kMetadata, clazz, definitionSupplier, reporter);
+ (KotlinClassMetadata.MultiFileClassPart) kMetadata,
+ packageName,
+ clazz,
+ definitionSupplier,
+ reporter,
+ keepByteCode);
} else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
return KotlinSyntheticClassInfo.create(
(KotlinClassMetadata.SyntheticClass) kMetadata,
+ packageName,
clazz,
kotlin,
definitionSupplier,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index 60851e2..d6ff5b4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -25,19 +25,19 @@
public static KotlinClassifierInfo create(
KmClassifier classifier, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
if (classifier instanceof KmClassifier.Class) {
- String typeName = ((KmClassifier.Class) classifier).getName();
+ String originalTypeName = ((KmClassifier.Class) classifier).getName();
// If this name starts with '.', it represents a local class or an anonymous object. This is
// used by the Kotlin compiler to prevent lookup of this name in the resolution:
// .kotlin/random/FallbackThreadLocalRandom$implStorage$1
- if (typeName.startsWith(".")) {
- return new KotlinUnknownClassClassifierInfo(typeName);
- }
- String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(typeName);
+ boolean isLocalOrAnonymous = originalTypeName.startsWith(".");
+ String descriptor =
+ DescriptorUtils.getDescriptorFromKotlinClassifier(
+ isLocalOrAnonymous ? originalTypeName.substring(1) : originalTypeName);
if (DescriptorUtils.isClassDescriptor(descriptor)) {
return new KotlinClassClassifierInfo(
- referenceTypeFromDescriptor(descriptor, definitionSupplier));
+ referenceTypeFromDescriptor(descriptor, definitionSupplier), isLocalOrAnonymous);
} else {
- return new KotlinUnknownClassClassifierInfo(typeName);
+ return new KotlinUnknownClassClassifierInfo(originalTypeName);
}
} else if (classifier instanceof KmClassifier.TypeAlias) {
return new KotlinTypeAliasClassifierInfo(((TypeAlias) classifier).getName());
@@ -55,22 +55,29 @@
public static class KotlinClassClassifierInfo extends KotlinClassifierInfo {
private final DexType type;
+ private final boolean isLocalOrAnonymous;
- private KotlinClassClassifierInfo(DexType type) {
+ private KotlinClassClassifierInfo(DexType type, boolean isLocalOrAnonymous) {
this.type = type;
+ this.isLocalOrAnonymous = isLocalOrAnonymous;
}
@Override
void rewrite(
KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
- String classifier;
if (appView.appInfo().wasPruned(type)) {
- classifier = ClassClassifiers.anyName;
- } else {
- DexString descriptor = namingLens.lookupDescriptor(type);
- classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+ visitor.visitClass(ClassClassifiers.anyName);
+ return;
}
- visitor.visitClass(classifier);
+ DexString descriptor = namingLens.lookupDescriptor(type);
+ // For local or anonymous classes, the classifier is prefixed with '.' and inner classes are
+ // separated with '$'.
+ if (isLocalOrAnonymous) {
+ visitor.visitClass(
+ "." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString()));
+ } else {
+ visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString()));
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index 6dc4d4a..cd349ab 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -19,10 +19,12 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import kotlinx.metadata.KmDeclarationContainer;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
import kotlinx.metadata.KmTypeAlias;
+import kotlinx.metadata.internal.metadata.deserialization.Flags;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -50,7 +52,8 @@
Map<String, DexEncodedMethod> methodSignatureMap,
Map<String, DexEncodedField> fieldSignatureMap,
DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ Reporter reporter,
+ Consumer<DexEncodedMethod> keepByteCode) {
ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder();
for (KmFunction kmFunction : container.getFunctions()) {
JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmFunction);
@@ -75,6 +78,7 @@
}
continue;
}
+ keepIfInline(kmFunction.getFlags(), method, keepByteCode);
method.setKotlinMemberInfo(kotlinFunctionInfo);
}
@@ -97,6 +101,7 @@
methodSignatureMap.get(propertyProcessor.getterSignature().asString());
if (method != null) {
hasBacking = true;
+ keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
method.setKotlinMemberInfo(kotlinPropertyInfo);
}
}
@@ -105,6 +110,7 @@
methodSignatureMap.get(propertyProcessor.setterSignature().asString());
if (method != null) {
hasBacking = true;
+ keepIfAccessorInline(kmProperty.getGetterFlags(), method, keepByteCode);
method.setKotlinMemberInfo(kotlinPropertyInfo);
}
}
@@ -118,6 +124,20 @@
notBackedProperties.build());
}
+ private static void keepIfInline(
+ int flags, DexEncodedMethod method, Consumer<DexEncodedMethod> keepByteCode) {
+ if (Flags.IS_INLINE.get(flags)) {
+ keepByteCode.accept(method);
+ }
+ }
+
+ private static void keepIfAccessorInline(
+ int flags, DexEncodedMethod method, Consumer<DexEncodedMethod> keepByteCode) {
+ if (Flags.IS_INLINE_ACCESSOR.get(flags)) {
+ keepByteCode.accept(method);
+ }
+ }
+
private static List<KotlinTypeAliasInfo> getTypeAliases(
List<KmTypeAlias> aliases, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
ImmutableList.Builder<KotlinTypeAliasInfo> builder = ImmutableList.builder();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index ccd957a..c31df8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -7,9 +7,11 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
+import java.util.function.Consumer;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -19,18 +21,24 @@
public class KotlinFileFacadeInfo implements KotlinClassLevelInfo {
private final KotlinPackageInfo packageInfo;
+ private final String packageName;
- private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo) {
+ private KotlinFileFacadeInfo(KotlinPackageInfo packageInfo, String packageName) {
this.packageInfo = packageInfo;
+ this.packageName = packageName;
}
public static KotlinFileFacadeInfo create(
FileFacade kmFileFacade,
+ String packageName,
DexClass clazz,
DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ Reporter reporter,
+ Consumer<DexEncodedMethod> keepByteCode) {
return new KotlinFileFacadeInfo(
- KotlinPackageInfo.create(kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter));
+ KotlinPackageInfo.create(
+ kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+ packageName);
}
@Override
@@ -52,4 +60,9 @@
kmPackage.accept(writer);
return writer.write().getHeader();
}
+
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
new file mode 100644
index 0000000..c8c8836
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, 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.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Reporter;
+import java.util.List;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+
+public class KotlinFlexibleTypeUpperBoundInfo extends KotlinTypeInfo {
+
+ private static final String KOTLIN_JVM_PLATFORMTYPE = "kotlin.jvm.PlatformType";
+ private static final KotlinFlexibleTypeUpperBoundInfo NO_FLEXIBLE_UPPER_BOUND =
+ new KotlinFlexibleTypeUpperBoundInfo(
+ 0, null, null, null, null, null, null, KOTLIN_JVM_PLATFORMTYPE);
+
+ private final String typeFlexibilityId;
+
+ private KotlinFlexibleTypeUpperBoundInfo(
+ int flags,
+ KotlinClassifierInfo classifier,
+ KotlinTypeInfo abbreviatedType,
+ KotlinTypeInfo outerType,
+ List<KotlinTypeProjectionInfo> arguments,
+ List<KotlinAnnotationInfo> annotations,
+ KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo,
+ String typeFlexibilityId) {
+ super(
+ flags,
+ classifier,
+ abbreviatedType,
+ outerType,
+ arguments,
+ annotations,
+ flexibleTypeUpperBoundInfo);
+ this.typeFlexibilityId = typeFlexibilityId;
+ assert KOTLIN_JVM_PLATFORMTYPE.equals(typeFlexibilityId);
+ }
+
+ static KotlinFlexibleTypeUpperBoundInfo create(
+ KmFlexibleTypeUpperBound flexibleTypeUpperBound,
+ DexDefinitionSupplier definitionSupplier,
+ Reporter reporter) {
+ if (flexibleTypeUpperBound == null) {
+ return NO_FLEXIBLE_UPPER_BOUND;
+ }
+ KmType kmType = flexibleTypeUpperBound.getType();
+ return new KotlinFlexibleTypeUpperBoundInfo(
+ kmType.getFlags(),
+ KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter),
+ KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
+ KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
+ getArguments(kmType.getArguments(), definitionSupplier, reporter),
+ KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+ KotlinFlexibleTypeUpperBoundInfo.create(
+ kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter),
+ flexibleTypeUpperBound.getTypeFlexibilityId());
+ }
+
+ public void rewrite(
+ KmVisitorProviders.KmFlexibleUpperBoundVisitorProvider visitorProvider,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens namingLens) {
+ if (this == NO_FLEXIBLE_UPPER_BOUND) {
+ // Nothing to do.
+ return;
+ }
+ super.rewrite(flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index d280d13..28928ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,25 +5,49 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
-import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.google.common.collect.Sets;
+import java.util.Set;
public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
private final AppView<?> appView;
+ private final DexDefinitionSupplier definitionSupplier;
+ private final Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
- public KotlinMetadataEnqueuerExtension(AppView<?> appView) {
+ public KotlinMetadataEnqueuerExtension(
+ AppView<?> appView, DexDefinitionSupplier definitionSupplier) {
this.appView = appView;
+ this.definitionSupplier = definitionSupplier;
}
@Override
- public void processNewlyLiveClass(
- DexProgramClass clazz, EnqueuerWorklist worklist, DexDefinitionSupplier definitionSupplier) {
- Kotlin kotlin = appView.dexItemFactory().kotlin;
- clazz.setKotlinInfo(
- KotlinClassMetadataReader.getKotlinInfo(
- kotlin, clazz, definitionSupplier, appView.options().reporter));
+ public void done(Enqueuer enqueuer) {
+ DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
+ DexClass kotlinMetadataClass =
+ appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
+ // We will process kotlin.Metadata even if the type is not present in the program, as long as
+ // the annotation will be in the output
+ boolean keepMetadata =
+ enqueuer.isPinned(kotlinMetadataType)
+ || enqueuer.isMissing(kotlinMetadataType)
+ || (kotlinMetadataClass != null && kotlinMetadataClass.isNotProgramClass());
+ enqueuer.forAllLiveClasses(
+ clazz -> {
+ clazz.setKotlinInfo(
+ KotlinClassMetadataReader.getKotlinInfo(
+ appView.dexItemFactory().kotlin,
+ clazz,
+ definitionSupplier,
+ appView.options().reporter,
+ !keepMetadata || !enqueuer.isPinned(clazz.type),
+ method -> keepByteCodeFunctions.add(method.method)));
+ });
+ appView.setCfByteCodePassThrough(keepByteCodeFunctions);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 31818e8..ce0ba80 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -9,11 +9,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueInt;
@@ -41,30 +38,8 @@
this.kotlin = factory.kotlin;
}
- public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexType type) {
- // TODO(b/154294232): Seems like this should never be called. Either we explicitly keep the
- // class and allow obfuscation - in which we should not remove it, or we should never have
- // metadata in the first place.
- DexProgramClass clazz = appView.definitionForProgramType(type);
- if (clazz == null) {
- return;
- }
- removeKotlinMetadataFromRenamedClass(appView, clazz);
- }
-
- public static void removeKotlinMetadataFromRenamedClass(AppView<?> appView, DexClass clazz) {
- // Remove @Metadata in DexAnnotation form if a class is renamed.
- clazz.setAnnotations(clazz.annotations().keepIf(anno -> isNotKotlinMetadata(appView, anno)));
- // Clear associated {@link KotlinInfo} to avoid accidentally deserialize it back to
- // DexAnnotation we've just removed above.
- if (clazz.isProgramClass()) {
- clazz.asProgramClass().setKotlinInfo(NO_KOTLIN_INFO);
- }
- }
-
private static boolean isNotKotlinMetadata(AppView<?> appView, DexAnnotation annotation) {
- return annotation.annotation.type
- != appView.dexItemFactory().kotlin.metadata.kotlinMetadataType;
+ return annotation.annotation.type != appView.dexItemFactory().kotlinMetadataType;
}
public void run(ExecutorService executorService) throws ExecutionException {
@@ -72,34 +47,40 @@
appView.appInfo().classes(),
clazz -> {
KotlinClassLevelInfo kotlinInfo = clazz.getKotlinInfo();
- DexAnnotation oldMeta =
- clazz.annotations().getFirstMatching(kotlin.metadata.kotlinMetadataType);
+ DexAnnotation oldMeta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
if (kotlinInfo == INVALID_KOTLIN_INFO) {
// Maintain invalid kotlin info for classes.
return;
}
- if (kotlinInfo == NO_KOTLIN_INFO) {
- assert oldMeta == null;
+ if (oldMeta == null
+ || kotlinInfo == NO_KOTLIN_INFO
+ || !appView.appInfo().isPinned(clazz.type)) {
+ // Remove @Metadata in DexAnnotation when there is no kotlin info and the type is not
+ // missing.
+ if (oldMeta != null) {
+ clazz.setAnnotations(
+ clazz.annotations().keepIf(anno -> isNotKotlinMetadata(appView, anno)));
+ }
return;
}
- if (oldMeta != null) {
- try {
- KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
- DexAnnotation newMeta = createKotlinMetadataAnnotation(kotlinClassHeader);
- clazz.setAnnotations(
- clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
- } catch (Throwable t) {
- appView
- .options()
- .reporter
- .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
- }
+ try {
+ KotlinClassHeader kotlinClassHeader = kotlinInfo.rewrite(clazz, appView, lens);
+ DexAnnotation newMeta =
+ createKotlinMetadataAnnotation(kotlinClassHeader, kotlinInfo.getPackageName());
+ clazz.setAnnotations(
+ clazz.annotations().rewrite(anno -> anno == oldMeta ? newMeta : anno));
+ } catch (Throwable t) {
+ appView
+ .options()
+ .reporter
+ .warning(KotlinMetadataDiagnostic.unexpectedErrorWhenRewriting(clazz.type, t));
}
},
executorService);
}
- private DexAnnotation createKotlinMetadataAnnotation(KotlinClassHeader header) {
+ private DexAnnotation createKotlinMetadataAnnotation(
+ KotlinClassHeader header, String packageName) {
List<DexAnnotationElement> elements = new ArrayList<>();
elements.add(
new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
@@ -119,14 +100,13 @@
new DexValueString(factory.createString(header.getExtraString()))));
elements.add(
new DexAnnotationElement(
- kotlin.metadata.packageName,
- new DexValueString(factory.createString(header.getPackageName()))));
+ kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
elements.add(
new DexAnnotationElement(
kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
DexEncodedAnnotation encodedAnnotation =
new DexEncodedAnnotation(
- kotlin.metadata.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
+ factory.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
return new DexAnnotation(DexAnnotation.VISIBILITY_RUNTIME, encodedAnnotation);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index e846c14..1f21451 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -10,11 +10,16 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.shaking.ProguardKeepRule;
+import com.android.tools.r8.shaking.ProguardKeepRuleType;
import com.android.tools.r8.utils.DescriptorUtils;
import kotlinx.metadata.KmExtensionType;
import kotlinx.metadata.KmProperty;
@@ -49,6 +54,11 @@
DexClass clazz, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
throw new Unreachable("Should never be called");
}
+
+ @Override
+ public String getPackageName() {
+ throw new Unreachable("Should never be called");
+ }
}
static JvmFieldSignature toJvmFieldSignature(DexField field) {
@@ -161,4 +171,47 @@
}
return type;
}
+
+ public static boolean mayProcessKotlinMetadata(AppView<?> appView) {
+ // This can run before we have determined the pinned items, because we may need to load the
+ // stack-map table on input. This is therefore a conservative guess on kotlin.Metadata is kept.
+ DexClass kotlinMetadata =
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(appView.dexItemFactory().kotlinMetadataType);
+ if (kotlinMetadata == null || kotlinMetadata.isNotProgramClass()) {
+ return true;
+ }
+ ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+ if (proguardConfiguration != null && proguardConfiguration.getRules() != null) {
+ for (ProguardConfigurationRule rule : proguardConfiguration.getRules()) {
+ if (KotlinMetadataUtils.canBeKotlinMetadataKeepRule(rule, appView.options().itemFactory)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static boolean canBeKotlinMetadataKeepRule(
+ ProguardConfigurationRule rule, DexItemFactory factory) {
+ if (rule.isProguardIfRule()) {
+ // For if rules, we simply assume that the precondition can become true.
+ return canBeKotlinMetadataKeepRule(rule.asProguardIfRule().getSubsequentRule(), factory);
+ }
+ if (!rule.isProguardKeepRule()) {
+ return false;
+ }
+ ProguardKeepRule proguardKeepRule = rule.asProguardKeepRule();
+ // -keepclassmembers will not in itself keep a class alive.
+ if (proguardKeepRule.getType() == ProguardKeepRuleType.KEEP_CLASS_MEMBERS) {
+ return false;
+ }
+ // If the rule allows shrinking, it will not require us to keep the class.
+ if (proguardKeepRule.getModifiers().allowsShrinking) {
+ return false;
+ }
+ // Check if the type is matched
+ return proguardKeepRule.getClassNames().matches(factory.kotlinMetadataType);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index 33a0aa6..fa21f81 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -23,6 +23,7 @@
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmLambda;
import kotlinx.metadata.KmPackage;
@@ -43,7 +44,7 @@
public static void writeKotlinMetadataAnnotation(
String prefix, DexAnnotation annotation, PrintStream ps, Kotlin kotlin) {
- assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
+ assert annotation.annotation.type == kotlin.factory.kotlinMetadataType;
try {
KotlinClassMetadata kMetadata =
KotlinClassMetadataReader.toKotlinClassMetadata(kotlin, annotation.annotation);
@@ -525,6 +526,33 @@
"outerType",
sb,
nextIndent -> appendKmType(newIndent, sb, kmType.getOuterType()));
+ KmFlexibleTypeUpperBound flexibleTypeUpperBound = kmType.getFlexibleTypeUpperBound();
+ if (flexibleTypeUpperBound != null) {
+ appendKeyValue(
+ newIndent,
+ "flexibleTypeUpperBound",
+ sb,
+ nextIndent -> {
+ appendKmSection(
+ newIndent,
+ "FlexibleTypeUpperBound",
+ sb,
+ nextNextIndent -> {
+ appendKeyValue(
+ nextNextIndent,
+ "typeFlexibilityId",
+ sb,
+ flexibleTypeUpperBound.getTypeFlexibilityId());
+ appendKeyValue(
+ nextNextIndent,
+ "type",
+ sb,
+ nextNextNextIndent ->
+ appendKmType(
+ nextNextNextIndent, sb, flexibleTypeUpperBound.getType()));
+ });
+ });
+ }
appendKeyValue(newIndent, "raw", sb, JvmExtensionsKt.isRaw(kmType) + "");
appendKeyValue(
newIndent,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 7eaa6b8..3baea9c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -23,20 +23,24 @@
public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo {
private final List<DexType> partClassNames;
+ private final String packageName;
- private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames) {
+ private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames, String packageName) {
this.partClassNames = partClassNames;
+ this.packageName = packageName;
}
static KotlinMultiFileClassFacadeInfo create(
- MultiFileClassFacade kmMultiFileClassFacade, DexDefinitionSupplier appView) {
+ MultiFileClassFacade kmMultiFileClassFacade,
+ String packageName,
+ DexDefinitionSupplier definitionSupplier) {
ImmutableList.Builder<DexType> builder = ImmutableList.builder();
for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(partClassName);
- DexType type = appView.dexItemFactory().createType(descriptor);
+ DexType type = definitionSupplier.dexItemFactory().createType(descriptor);
builder.add(type);
}
- return new KotlinMultiFileClassFacadeInfo(builder.build());
+ return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName);
}
@Override
@@ -64,4 +68,9 @@
}
return writer.write(partClassNameStrings).getHeader();
}
+
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index 6a06b7d..d4ec800 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -7,9 +7,11 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
+import java.util.function.Consumer;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -20,20 +22,27 @@
private final String facadeClassName;
private final KotlinPackageInfo packageInfo;
+ private final String packageName;
- private KotlinMultiFileClassPartInfo(String facadeClassName, KotlinPackageInfo packageInfo) {
+ private KotlinMultiFileClassPartInfo(
+ String facadeClassName, KotlinPackageInfo packageInfo, String packageName) {
this.facadeClassName = facadeClassName;
this.packageInfo = packageInfo;
+ this.packageName = packageName;
}
static KotlinMultiFileClassPartInfo create(
MultiFileClassPart classPart,
+ String packageName,
DexClass clazz,
DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ Reporter reporter,
+ Consumer<DexEncodedMethod> keepByteCode) {
return new KotlinMultiFileClassPartInfo(
classPart.getFacadeClassName(),
- KotlinPackageInfo.create(classPart.toKmPackage(), clazz, definitionSupplier, reporter));
+ KotlinPackageInfo.create(
+ classPart.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+ packageName);
}
@Override
@@ -56,4 +65,9 @@
kmPackage.accept(writer);
return writer.write(facadeClassName).getHeader();
}
+
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index 7a667e6..d98e109 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.utils.Reporter;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Consumer;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.JvmExtensionsKt;
@@ -35,7 +36,8 @@
KmPackage kmPackage,
DexClass clazz,
DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ Reporter reporter,
+ Consumer<DexEncodedMethod> keepByteCode) {
Map<String, DexEncodedField> fieldMap = new HashMap<>();
for (DexEncodedField field : clazz.fields()) {
fieldMap.put(toJvmFieldSignature(field.field).asString(), field);
@@ -47,7 +49,7 @@
return new KotlinPackageInfo(
JvmExtensionsKt.getModuleName(kmPackage),
KotlinDeclarationContainerInfo.create(
- kmPackage, methodMap, fieldMap, definitionSupplier, reporter));
+ kmPackage, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode));
}
public void rewrite(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index af02060..38933b5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -20,6 +20,7 @@
public class KotlinSyntheticClassInfo implements KotlinClassLevelInfo {
private final KotlinLambdaInfo lambda;
+ private final String packageName;
public enum Flavour {
KotlinStyleLambda,
@@ -29,13 +30,15 @@
private final Flavour flavour;
- private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour) {
+ private KotlinSyntheticClassInfo(KotlinLambdaInfo lambda, Flavour flavour, String packageName) {
this.lambda = lambda;
this.flavour = flavour;
+ this.packageName = packageName;
}
static KotlinSyntheticClassInfo create(
SyntheticClass syntheticClass,
+ String packageName,
DexClass clazz,
Kotlin kotlin,
DexDefinitionSupplier definitionSupplier,
@@ -49,7 +52,8 @@
lambda != null
? KotlinLambdaInfo.create(clazz, lambda, definitionSupplier, reporter)
: null,
- getFlavour(syntheticClass, clazz, kotlin));
+ getFlavour(syntheticClass, clazz, kotlin),
+ packageName);
}
public boolean isLambda() {
@@ -87,6 +91,11 @@
return writer.write().getHeader();
}
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
+
private static Flavour getFlavour(
KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
// Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 204bcbb..8601667 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -28,25 +28,23 @@
private final KotlinTypeInfo outerType;
private final List<KotlinTypeProjectionInfo> arguments;
private final List<KotlinAnnotationInfo> annotations;
- // TODO(b/154351125): Extend with flexible upper bounds
+ private final KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo;
- private KotlinTypeInfo(
+ KotlinTypeInfo(
int flags,
KotlinClassifierInfo classifier,
KotlinTypeInfo abbreviatedType,
KotlinTypeInfo outerType,
List<KotlinTypeProjectionInfo> arguments,
- List<KotlinAnnotationInfo> annotations) {
+ List<KotlinAnnotationInfo> annotations,
+ KotlinFlexibleTypeUpperBoundInfo flexibleTypeUpperBoundInfo) {
this.flags = flags;
this.classifier = classifier;
this.abbreviatedType = abbreviatedType;
this.outerType = outerType;
this.arguments = arguments;
this.annotations = annotations;
- }
-
- public List<KotlinTypeProjectionInfo> getArguments() {
- return arguments;
+ this.flexibleTypeUpperBoundInfo = flexibleTypeUpperBoundInfo;
}
static KotlinTypeInfo create(
@@ -60,10 +58,12 @@
KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
getArguments(kmType.getArguments(), definitionSupplier, reporter),
- KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier));
+ KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+ KotlinFlexibleTypeUpperBoundInfo.create(
+ kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter));
}
- private static List<KotlinTypeProjectionInfo> getArguments(
+ static List<KotlinTypeProjectionInfo> getArguments(
List<KmTypeProjection> projections,
DexDefinitionSupplier definitionSupplier,
Reporter reporter) {
@@ -94,6 +94,8 @@
argument.rewrite(
kmTypeVisitor::visitArgument, kmTypeVisitor::visitStarProjection, appView, namingLens);
}
+ flexibleTypeUpperBoundInfo.rewrite(
+ kmTypeVisitor::visitFlexibleTypeUpperBound, appView, namingLens);
if (annotations.isEmpty()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d7797a4..0c223b1 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardPackageNameList;
@@ -129,9 +128,6 @@
if (!renaming.containsKey(clazz.type)) {
DexString renamed = computeName(clazz.type);
renaming.put(clazz.type, renamed);
- if (!appView.options().enableKotlinMetadataRewritingForRenamedClasses) {
- KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, clazz);
- }
// If the class is a member class and it has used $ separator, its renamed name should have
// the same separator (as long as inner-class attribute is honored).
assert !keepInnerClassStructure
@@ -350,7 +346,6 @@
// and then use that renamed name as a base prefix for the current inner class.
renamed = computeName(outer);
renaming.put(outer, renamed);
- KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, outer);
}
String binaryName = getClassBinaryNameFromDescriptor(renamed.toString());
state = new Namespace(binaryName, innerClassSeparator);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index ab735ca..3ce8e7f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -201,9 +201,6 @@
// TODO(b/133091438) assert that !dexClass.isLibraryClass();
DexString mappedName = factory.createString(classNaming.renamedName);
checkAndAddMappedNames(type, mappedName, classNaming.position);
- if (dexClass != null) {
- KotlinMetadataRewriter.removeKotlinMetadataFromRenamedClass(appView, dexClass);
- }
classNaming.forAllMemberNaming(
memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
} else {
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 1cdeec3..31e239b 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -5,15 +5,25 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -23,7 +33,12 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
@@ -52,6 +67,9 @@
*/
public class BridgeHoisting {
+ private static final OptimizationFeedbackSimple feedback =
+ OptimizationFeedbackSimple.getInstance();
+
private final AppView<AppInfoWithLiveness> appView;
// A lens that keeps track of the changes for construction of the Proguard map.
@@ -96,10 +114,7 @@
for (DexProgramClass subclass : subclasses) {
for (DexEncodedMethod method : subclass.virtualMethods()) {
BridgeInfo bridgeInfo = method.getOptimizationInfo().getBridgeInfo();
- // TODO(b/153147967): Even if the bridge is not targeting a method in the superclass, it may
- // be possible to rewrite the bridge to target a method in the superclass, such that we can
- // hoist it. Add a test.
- if (bridgeInfo != null && bridgeIsTargetingMethodInSuperclass(subclass, bridgeInfo)) {
+ if (bridgeInfo != null) {
candidates.add(equivalence.wrap(method.method));
}
}
@@ -150,9 +165,10 @@
return;
}
- // Go through each of the subclasses and bail-out if each subclass does not declare the same
- // bridge.
- BridgeInfo firstBridgeInfo = null;
+ // Go through each of the subclasses and find the bridges that can be hoisted. The bridge holder
+ // classes are stored in buckets grouped by the behavior of the body of the bridge (which is
+ // implicitly defined by the signature of the invoke-virtual instruction).
+ Map<Wrapper<DexMethod>, List<DexProgramClass>> eligibleVirtualInvokeBridges = new HashMap<>();
for (DexProgramClass subclass : subclasses) {
DexEncodedMethod definition = subclass.lookupVirtualMethod(method);
if (definition == null) {
@@ -172,44 +188,88 @@
// should never be the case in practice.
continue;
}
+
+ // Hoisting would change the program behavior.
return;
}
BridgeInfo currentBridgeInfo = definition.getOptimizationInfo().getBridgeInfo();
if (currentBridgeInfo == null) {
- return;
+ // This is not a bridge, so the method needs to remain on the subclass.
+ continue;
}
- if (firstBridgeInfo == null) {
- firstBridgeInfo = currentBridgeInfo;
- } else if (!currentBridgeInfo.hasSameTarget(firstBridgeInfo)) {
- return;
- }
+ assert currentBridgeInfo.isVirtualBridgeInfo();
+
+ VirtualBridgeInfo currentVirtualBridgeInfo = currentBridgeInfo.asVirtualBridgeInfo();
+ DexMethod invokedMethod = currentVirtualBridgeInfo.getInvokedMethod();
+ Wrapper<DexMethod> wrapper = MethodSignatureEquivalence.get().wrap(invokedMethod);
+ eligibleVirtualInvokeBridges
+ .computeIfAbsent(wrapper, ignore -> new ArrayList<>())
+ .add(subclass);
}
- // If we reached this point, it is because all of the subclasses define the same bridge.
- assert firstBridgeInfo != null;
+ // There should be at least one method that is eligible for hoisting.
+ assert !eligibleVirtualInvokeBridges.isEmpty();
+
+ Entry<Wrapper<DexMethod>, List<DexProgramClass>> mostFrequentBridge =
+ findMostFrequentBridge(eligibleVirtualInvokeBridges);
+ assert mostFrequentBridge != null;
+ DexMethod invokedMethod = mostFrequentBridge.getKey().get();
+ List<DexProgramClass> eligibleSubclasses = mostFrequentBridge.getValue();
// Choose one of the bridge definitions as the one that we will be moving to the superclass.
- ProgramMethod representative = findRepresentative(subclasses, method);
- assert representative != null;
+ ProgramMethod representative = findRepresentative(eligibleSubclasses, method);
// Guard against accessibility issues.
if (mayBecomeInaccessibleAfterHoisting(clazz, representative)) {
return;
}
- // Move the bridge method to the super class, and record this in the graph lens.
- DexMethod newMethod =
- appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
- clazz.addVirtualMethod(representative.getDefinition().toTypeSubstitutedMethod(newMethod));
- lensBuilder.move(representative.getDefinition().method, newMethod);
+ // Rewrite the invoke-virtual instruction to target the virtual method on the new holder class.
+ // Otherwise the code might not type check.
+ DexMethod methodToInvoke =
+ appView.dexItemFactory().createMethod(clazz.type, invokedMethod.proto, invokedMethod.name);
- // Remove all of the bridges in the subclasses.
- for (DexProgramClass subclass : subclasses) {
- DexEncodedMethod removed = subclass.removeMethod(method);
- assert removed == null || !appView.appInfo().isPinned(removed.method);
+ // The targeted method must be present on the new holder class for this to be feasible.
+ ResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOnClass(methodToInvoke, clazz);
+ if (!resolutionResult.isSingleResolution()) {
+ return;
}
+
+ // Now update the code of the bridge method chosen as representative.
+ representative
+ .getDefinition()
+ .setCode(createCodeForVirtualBridge(representative, methodToInvoke), appView);
+ feedback.setBridgeInfo(representative.getDefinition(), new VirtualBridgeInfo(methodToInvoke));
+
+ // Move the bridge method to the super class, and record this in the graph lens.
+ DexMethod newMethodReference =
+ appView.dexItemFactory().createMethod(clazz.type, method.proto, method.name);
+ DexEncodedMethod newMethod =
+ representative.getDefinition().toTypeSubstitutedMethod(newMethodReference);
+ clazz.addVirtualMethod(newMethod);
+ lensBuilder.move(representative.getReference(), newMethodReference);
+
+ // Remove all of the bridges in the eligible subclasses.
+ for (DexProgramClass subclass : eligibleSubclasses) {
+ DexEncodedMethod removed = subclass.removeMethod(method);
+ assert removed != null && !appView.appInfo().isPinned(removed.method);
+ }
+ }
+
+ private static Entry<Wrapper<DexMethod>, List<DexProgramClass>> findMostFrequentBridge(
+ Map<Wrapper<DexMethod>, List<DexProgramClass>> eligibleVirtualInvokeBridges) {
+ Entry<Wrapper<DexMethod>, List<DexProgramClass>> result = null;
+ for (Entry<Wrapper<DexMethod>, List<DexProgramClass>> candidate :
+ eligibleVirtualInvokeBridges.entrySet()) {
+ List<DexProgramClass> eligibleSubclassesCandidate = candidate.getValue();
+ if (result == null || eligibleSubclassesCandidate.size() > result.getValue().size()) {
+ result = candidate;
+ }
+ }
+ return result;
}
private ProgramMethod findRepresentative(Iterable<DexProgramClass> subclasses, DexMethod method) {
@@ -219,7 +279,7 @@
return new ProgramMethod(subclass, definition);
}
}
- return null;
+ throw new Unreachable();
}
private boolean mayBecomeInaccessibleAfterHoisting(
@@ -230,6 +290,64 @@
return !representative.getDefinition().isPublic();
}
+ private Code createCodeForVirtualBridge(ProgramMethod representative, DexMethod methodToInvoke) {
+ Code code = representative.getDefinition().getCode();
+ if (code.isCfCode()) {
+ return createCfCodeForVirtualBridge(code.asCfCode(), methodToInvoke);
+ }
+ if (code.isDexCode()) {
+ return createDexCodeForVirtualBridge(code.asDexCode(), methodToInvoke);
+ }
+ throw new Unreachable("Unexpected code object of type " + code.getClass().getTypeName());
+ }
+
+ private CfCode createCfCodeForVirtualBridge(CfCode code, DexMethod methodToInvoke) {
+ List<CfInstruction> newInstructions = new ArrayList<>();
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction.isInvoke()) {
+ CfInvoke invoke = instruction.asInvoke();
+ assert invoke.isInvokeVirtual();
+ assert !invoke.isInterface();
+ assert invoke.getMethod().match(methodToInvoke);
+ newInstructions.add(new CfInvoke(invoke.getOpcode(), methodToInvoke, false));
+ } else {
+ newInstructions.add(instruction);
+ }
+ }
+ return new CfCode(
+ methodToInvoke.holder,
+ code.getMaxStack(),
+ code.getMaxLocals(),
+ newInstructions,
+ code.getTryCatchRanges(),
+ code.getLocalVariables());
+ }
+
+ private DexCode createDexCodeForVirtualBridge(DexCode code, DexMethod methodToInvoke) {
+ Instruction[] newInstructions = new Instruction[code.instructions.length];
+ for (int i = 0; i < code.instructions.length; i++) {
+ Instruction instruction = code.instructions[i];
+ if (instruction.isInvokeVirtual()) {
+ InvokeVirtual invoke = instruction.asInvokeVirtual();
+ InvokeVirtual newInvoke =
+ new InvokeVirtual(
+ invoke.A, methodToInvoke, invoke.C, invoke.D, invoke.E, invoke.F, invoke.G);
+ newInvoke.setOffset(invoke.getOffset());
+ newInstructions[i] = newInvoke;
+ } else {
+ newInstructions[i] = instruction;
+ }
+ }
+ return new DexCode(
+ code.registerSize,
+ code.incomingRegisterSize,
+ code.outgoingRegisterSize,
+ newInstructions,
+ code.tries,
+ code.handlers,
+ code.getDebugInfo());
+ }
+
static class BridgeHoistingLens extends NestedGraphLense {
public BridgeHoistingLens(
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 5da8e5f..44efffc 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -100,7 +100,7 @@
if (field.isPublic()) {
return;
}
- if (appView.appInfo().isPinned(field.field)) {
+ if (!appView.appInfo().isAccessModificationAllowed(field.field)) {
// TODO(b/131130038): Also do not publicize package-private and protected fields that
// are kept.
if (field.isPrivate()) {
@@ -138,7 +138,7 @@
return false;
}
// If this method is mentioned in keep rules, do not transform (rule applications changed).
- if (appView.appInfo().isPinned(method.method)) {
+ if (!appView.appInfo().isAccessModificationAllowed(method.method)) {
// TODO(b/131130038): Also do not publicize package-private and protected methods that are
// kept.
if (method.isPrivate()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index a3e4666..b992f8d 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.optimize;
-import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -301,64 +302,39 @@
private void computeFieldRebindingForIndirectAccessWithContexts(
DexField field, ProgramMethodSet contexts) {
- DexEncodedField target = appView.appInfo().resolveField(field).getResolvedField();
- if (target == null) {
- assert false;
+ SuccessfulFieldResolutionResult resolutionResult =
+ appView.appInfo().resolveField(field).asSuccessfulResolution();
+ if (resolutionResult == null) {
return;
}
- if (target.field == field) {
+ DexEncodedField resolvedField = resolutionResult.getResolvedField();
+ if (resolvedField.field == field) {
assert false;
return;
}
// Rebind to the lowest library class or program class. Do not rebind accesses to fields that
// are not visible from the access context.
- if (contexts.stream()
- .allMatch(
- context ->
- isMemberVisibleFromOriginalContext(
- appView, context, target.holder(), target.accessFlags))) {
+ boolean accessibleInAllContexts = true;
+ for (ProgramMethod context : contexts) {
+ boolean inaccessibleInContext =
+ AccessControl.isFieldAccessible(
+ resolvedField, resolutionResult.getResolvedHolder(), context, appView)
+ .isPossiblyFalse();
+ if (inaccessibleInContext) {
+ accessibleInAllContexts = false;
+ break;
+ }
+ }
+
+ if (accessibleInAllContexts) {
builder.map(
- field, lense.lookupField(validTargetFor(target.field, field, DexClass::lookupField)));
+ field,
+ lense.lookupField(validTargetFor(resolvedField.field, field, DexClass::lookupField)));
}
}
- public static boolean isTypeVisibleFromContext(
- AppView<?> appView, ProgramMethod context, DexType type) {
- DexType baseType = type.toBaseType(appView.dexItemFactory());
- if (baseType.isPrimitiveType()) {
- return true;
- }
- return isClassTypeVisibleFromContext(appView, context, baseType);
- }
-
- public static boolean isClassTypeVisibleFromContext(
- AppView<?> appView, ProgramMethod context, DexType type) {
- assert type.isClassType();
- DexClass clazz = appView.definitionFor(type);
- return clazz != null && isClassTypeVisibleFromContext(appView, context, clazz);
- }
-
- public static boolean isClassTypeVisibleFromContext(
- AppView<?> appView, ProgramMethod context, DexClass clazz) {
- ConstraintWithTarget classVisibility =
- ConstraintWithTarget.deriveConstraint(
- context.getHolder(), clazz.type, clazz.accessFlags, appView);
- return classVisibility != ConstraintWithTarget.NEVER;
- }
-
- public static boolean isMemberVisibleFromOriginalContext(
- AppView<?> appView, ProgramMethod context, DexType holder, AccessFlags<?> memberAccessFlags) {
- if (!isClassTypeVisibleFromContext(appView, context, holder)) {
- return false;
- }
- ConstraintWithTarget memberVisibility =
- ConstraintWithTarget.deriveConstraint(
- context.getHolder(), holder, memberAccessFlags, appView);
- return memberVisibility != ConstraintWithTarget.NEVER;
- }
-
public GraphLense run() {
AppInfoWithLiveness appInfo = appView.appInfo();
// Virtual invokes are on classes, so use class resolution.
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
index b595f87..959976a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceInvalidStackTraceLineDiagnostics.java
@@ -40,11 +40,11 @@
return message;
}
- public static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
+ static RetraceInvalidStackTraceLineDiagnostics createNull(int lineNumber) {
return new RetraceInvalidStackTraceLineDiagnostics(lineNumber, NULL_STACK_TRACE_LINE_MESSAGE);
}
- public static RetraceInvalidStackTraceLineDiagnostics createParse(int lineNumber, String line) {
+ static RetraceInvalidStackTraceLineDiagnostics createParse(int lineNumber, String line) {
return new RetraceInvalidStackTraceLineDiagnostics(
lineNumber, String.format(PARSE_STACK_TRACE_LINE_MESSAGE, line));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 308cf69..275b882 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -138,6 +138,8 @@
public final Set<DexCallSite> callSites;
/** Set of all items that have to be kept independent of whether they are used. */
final Set<DexReference> pinnedItems;
+ /** Set of kept items that are allowed to be publicized. */
+ final Set<DexReference> allowAccessModification;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
/** All items with assumenosideeffects rule. */
@@ -216,6 +218,7 @@
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
Set<DexReference> pinnedItems,
+ Set<DexReference> allowAccessModification,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
@@ -251,6 +254,7 @@
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.pinnedItems = pinnedItems;
+ this.allowAccessModification = allowAccessModification;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
@@ -301,6 +305,7 @@
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
Set<DexReference> pinnedItems,
+ Set<DexReference> allowAccessModification,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
@@ -336,6 +341,7 @@
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.pinnedItems = pinnedItems;
+ this.allowAccessModification = allowAccessModification;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
@@ -387,6 +393,7 @@
previous.staticInvokes,
previous.callSites,
previous.pinnedItems,
+ previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
previous.assumedValues,
@@ -439,6 +446,7 @@
additionalPinnedItems == null
? previous.pinnedItems
: CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
+ previous.allowAccessModification,
previous.mayHaveSideEffects,
previous.noSideEffects,
previous.assumedValues,
@@ -484,6 +492,7 @@
this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
this.pinnedItems = previous.pinnedItems;
+ this.allowAccessModification = previous.allowAccessModification;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.noSideEffects = previous.noSideEffects;
this.assumedValues = previous.assumedValues;
@@ -518,21 +527,10 @@
return new AppInfoWithLivenessModifier();
}
- private boolean assertDefinitionFor = true;
-
- public void disableDefinitionForAssert() {
- assertDefinitionFor = false;
- }
-
- public void enableDefinitionForAssert() {
- assertDefinitionFor = true;
- }
-
@Override
public DexClass definitionFor(DexType type) {
DexClass definition = super.definitionFor(type);
- assert !assertDefinitionFor
- || definition != null
+ assert definition != null
|| deadProtoTypes.contains(type)
|| missingTypes.contains(type)
// TODO(b/150693139): Remove these exceptions once fixed.
@@ -866,6 +864,14 @@
return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
}
+ public boolean mayPropagateArgumentsTo(ProgramMethod method) {
+ DexMethod reference = method.getReference();
+ return method.getDefinition().hasCode()
+ && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
+ && !neverReprocess.contains(reference)
+ && !pinnedItems.contains(reference);
+ }
+
public boolean mayPropagateValueFor(DexReference reference) {
assert checkIfObsolete();
return options().enableValuePropagation
@@ -919,6 +925,11 @@
return this;
}
+ public boolean isAccessModificationAllowed(DexReference reference) {
+ assert options().getProguardConfiguration().isAccessModificationAllowed();
+ return allowAccessModification.contains(reference) || !isPinned(reference);
+ }
+
public boolean isPinned(DexReference reference) {
assert checkIfObsolete();
return pinnedItems.contains(reference);
@@ -1014,6 +1025,7 @@
// after second tree shaking.
callSites,
lens.rewriteReferencesConservatively(pinnedItems),
+ lens.rewriteReferencesConservatively(allowAccessModification),
rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
rewriteReferenceKeys(noSideEffects, lens::lookupReference),
rewriteReferenceKeys(assumedValues, lens::lookupReference),
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f2dddae..6e8f93b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -135,6 +135,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import org.objectweb.asm.Opcodes;
@@ -189,7 +190,7 @@
private ProguardClassFilter dontWarnPatterns;
private final EnqueuerUseRegistryFactory useRegistryFactory;
private AnnotationRemover.Builder annotationRemoverBuilder;
- private final DexDefinitionSupplier enqueuerDefinitionSupplier;
+ private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
private final Map<DexMethod, ProgramMethodSet> virtualInvokes = new IdentityHashMap<>();
private final Map<DexMethod, ProgramMethodSet> interfaceInvokes = new IdentityHashMap<>();
@@ -308,6 +309,13 @@
private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
/**
+ * A set of references that we are keeping due to keep rules, which we are allowed to publicize.
+ */
+ // TODO(b/156715504): This should be maintained in a structure that describes what we are allowed
+ // and not allowed to do with program items that are referenced from keep rules.
+ private final Map<DexReference, OptionalBool> allowAccessModification = new IdentityHashMap<>();
+
+ /**
* A set of seen const-class references that both serve as an initial lock-candidate set and will
* prevent statically merging the classes referenced.
*/
@@ -662,7 +670,7 @@
} else {
throw new IllegalArgumentException(item.toString());
}
- pinnedItems.add(item.toReference());
+ addPinnedItem(item.toReference(), rules);
}
private void enqueueFirstNonSerializableClassInitializer(
@@ -1536,8 +1544,7 @@
compatEnqueueHolderIfDependentNonStaticMember(
holder, rootSet.getDependentKeepClassCompatRule(holder.getType()));
- analyses.forEach(
- analysis -> analysis.processNewlyLiveClass(holder, workList, enqueuerDefinitionSupplier));
+ analyses.forEach(analysis -> analysis.processNewlyLiveClass(holder, workList));
}
private void ensureMethodsContinueToWidenAccess(DexClass clazz) {
@@ -1794,11 +1801,11 @@
(dexType, ignored) -> {
if (holder.isProgramClass()) {
DexReference holderReference = holder.toReference();
- pinnedItems.add(holderReference);
+ addPinnedItem(holderReference);
rootSet.shouldNotBeMinified(holderReference);
for (DexEncodedMember<?, ?> member : holder.members()) {
DexMember<?, ?> memberReference = member.toReference();
- pinnedItems.add(memberReference);
+ addPinnedItem(memberReference);
rootSet.shouldNotBeMinified(memberReference);
}
}
@@ -2339,6 +2346,10 @@
return liveNonProgramTypes.contains(clazz);
}
+ public void forAllLiveClasses(Consumer<DexProgramClass> consumer) {
+ liveTypes.items.forEach(consumer);
+ }
+
// Package protected due to entry point from worklist.
void markInstanceFieldAsReachable(ProgramField field, KeepReason reason) {
if (Log.ENABLED) {
@@ -2509,7 +2520,7 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking for not renaming it is in the root set.
workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
- pinnedItems.add(valuesMethod.toReference());
+ addPinnedItem(valuesMethod.toReference());
rootSet.shouldNotBeMinified(valuesMethod.toReference());
}
}
@@ -2578,11 +2589,12 @@
throws ExecutionException {
this.rootSet = rootSet;
this.dontWarnPatterns = dontWarnPatterns;
- if (!options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations
- && mode.isInitialTreeShaking()) {
- registerAnalysis(new KotlinMetadataEnqueuerExtension(appView));
- }
// Translate the result of root-set computation into enqueuer actions.
+ if (appView.options().getProguardConfiguration() != null
+ && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations
+ && mode.isInitialTreeShaking()) {
+ registerAnalysis(new KotlinMetadataEnqueuerExtension(appView, enqueuerDefinitionSupplier));
+ }
if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
enqueueRootItems(rootSet.noShrinking);
} else {
@@ -2603,7 +2615,7 @@
trace(executorService, timing);
options.reporter.failIfPendingErrors();
finalizeLibraryMethodOverrideInformation();
- analyses.forEach(EnqueuerAnalysis::done);
+ analyses.forEach(analyses -> analyses.done(this));
assert verifyKeptGraph();
AppInfoWithLiveness appInfoWithLiveness = createAppInfo(appInfo);
if (options.testing.enqueuerInspector != null) {
@@ -2612,6 +2624,39 @@
return appInfoWithLiveness;
}
+ public boolean isPinned(DexReference reference) {
+ return pinnedItems.contains(reference);
+ }
+
+ private boolean addPinnedItem(DexReference reference) {
+ allowAccessModification.put(reference, OptionalBool.unknown());
+ return pinnedItems.add(reference);
+ }
+
+ private boolean addPinnedItem(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+ assert rules != null;
+ assert !rules.isEmpty();
+ OptionalBool allowAccessModificationOfReference =
+ allowAccessModification.getOrDefault(reference, OptionalBool.TRUE);
+ if (allowAccessModificationOfReference.isTrue()) {
+ for (ProguardKeepRuleBase rule : rules) {
+ ProguardKeepRuleModifiers modifiers =
+ (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
+ .getModifiers();
+ if (!modifiers.allowsAccessModification) {
+ allowAccessModificationOfReference = OptionalBool.FALSE;
+ break;
+ }
+ }
+ allowAccessModification.put(reference, allowAccessModificationOfReference);
+ }
+ return pinnedItems.add(reference);
+ }
+
+ public boolean isMissing(DexType type) {
+ return missingTypes.contains(type);
+ }
+
private static class SyntheticAdditions {
Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
@@ -2674,7 +2719,7 @@
// All synthetic additions are initial tree shaking only. No need to track keep reasons.
KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
- enqueuer.pinnedItems.addAll(pinnedMethods);
+ pinnedMethods.forEach(enqueuer::addPinnedItem);
for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
syntheticInstantiations.values()) {
enqueuer.workList.enqueueMarkInstantiatedAction(
@@ -2789,6 +2834,9 @@
// to a static method will invalidate the reachable method sets for tracing methods.
ensureLambdaAccessibility();
+ // Remove the items from `allowAccessModification` that we are not allowed to publicize.
+ allowAccessModification.entrySet().removeIf(entry -> !entry.getValue().isTrue());
+
// Compute the set of dead proto types.
deadProtoTypeCandidates.removeIf(this::isTypeLive);
@@ -2860,6 +2908,7 @@
toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
callSites,
pinnedItems,
+ allowAccessModification.keySet(),
rootSet.mayHaveSideEffects,
rootSet.noSideEffects,
rootSet.assumedValues,
@@ -3538,7 +3587,7 @@
workList.enqueueMarkInstantiatedAction(
clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
}
- if (pinnedItems.add(encodedField.field)) {
+ if (addPinnedItem(encodedField.field)) {
markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
}
} else {
@@ -3725,14 +3774,14 @@
// Add this interface to the set of pinned items to ensure that we do not merge the
// interface into its unique subtype, if any.
// TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
- pinnedItems.add(clazz.type);
+ addPinnedItem(clazz.type);
KeepReason reason = KeepReason.reflectiveUseIn(method);
markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
// Also pin all of its virtual methods to ensure that the devirtualizer does not perform
// illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
- pinnedItems.add(virtualMethod.method);
+ addPinnedItem(virtualMethod.method);
markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 0b02b70..d00bc9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -87,6 +87,10 @@
return Collections::emptyIterator;
}
+ public boolean hasWildcards() {
+ return getWildcards().iterator().hasNext();
+ }
+
static Iterable<ProguardWildcard> getWildcardsOrEmpty(ProguardClassNameList nameList) {
return nameList == null ? Collections::emptyIterator : nameList.getWildcards();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5b1f672..aee4a83 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -27,7 +27,6 @@
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
-import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.io.File;
@@ -179,7 +178,7 @@
reporter.error(new StringDiagnostic("Failed to read file: " + e.getMessage(),
source.getOrigin()));
} catch (ProguardRuleParserException e) {
- reporter.error(e, MoreObjects.firstNonNull(e.getCause(), e));
+ reporter.error(e);
}
}
reporter.failIfPendingErrors();
@@ -237,6 +236,12 @@
|| parseTestingOption(optionStart)
|| parseUnsupportedOptionAndErr(optionStart)) {
// Intentionally left empty.
+ } else if (acceptString("adaptkotlinmetadata")) {
+ reporter.info(
+ new StringDiagnostic(
+ "Ignoring -adaptkotlinmetadata because R8 always process kotlin.Metadata",
+ origin,
+ getPosition(optionStart)));
} else if (acceptString("renamesourcefileattribute")) {
skipWhitespace();
if (isOptionalArgumentGiven()) {
@@ -634,11 +639,10 @@
parseClassSpec(keepRuleBuilder, true);
return true;
} catch (ProguardRuleParserException e) {
- throw reporter.fatalError(e, MoreObjects.firstNonNull(e.getCause(), e));
+ throw reporter.fatalError(e);
}
}
return false;
-
}
private boolean parseOptimizationOption(TextPosition optionStart)
@@ -949,6 +953,8 @@
builder.getModifiersBuilder().setAllowsOptimization(true);
} else if (acceptString("obfuscation")) {
builder.getModifiersBuilder().setAllowsObfuscation(true);
+ } else if (acceptString("accessmodification")) {
+ builder.getModifiersBuilder().setAllowsAccessModification(true);
}
} else if (acceptString("includedescriptorclasses")) {
builder.getModifiersBuilder().setIncludeDescriptorClasses(true);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 52c3b4c..0e21e66 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -58,6 +58,14 @@
return null;
}
+ public boolean isProguardIfRule() {
+ return false;
+ }
+
+ public ProguardIfRule asProguardIfRule() {
+ return null;
+ }
+
public boolean isClassInlineRule() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index d4e2b1b..ba9ec8a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -30,6 +30,10 @@
return preconditions;
}
+ public ProguardKeepRule getSubsequentRule() {
+ return subsequentRule;
+ }
+
public static class Builder extends ProguardKeepRuleBase.Builder<ProguardIfRule, Builder> {
ProguardKeepRule subsequentRule = null;
@@ -102,6 +106,16 @@
return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
}
+ @Override
+ public boolean isProguardIfRule() {
+ return true;
+ }
+
+ @Override
+ public ProguardIfRule asProguardIfRule() {
+ return this;
+ }
+
protected ProguardIfRule materialize(
DexItemFactory dexItemFactory, Set<DexReference> preconditions) {
return new ProguardIfRule(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index eb2acee..a02117d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -6,6 +6,7 @@
public class ProguardKeepRuleModifiers {
public static class Builder {
+ private boolean allowsAccessModification = false;
private boolean allowsShrinking = false;
private boolean allowsOptimization = false;
private boolean allowsObfuscation = false;
@@ -13,6 +14,11 @@
private Builder() {}
+ public Builder setAllowsAccessModification(boolean allowsAccessModification) {
+ this.allowsAccessModification = allowsAccessModification;
+ return this;
+ }
+
public void setAllowsShrinking(boolean allowsShrinking) {
this.allowsShrinking = allowsShrinking;
}
@@ -31,26 +37,34 @@
}
ProguardKeepRuleModifiers build() {
- return new ProguardKeepRuleModifiers(allowsShrinking, allowsOptimization, allowsObfuscation,
+ return new ProguardKeepRuleModifiers(
+ allowsAccessModification,
+ allowsShrinking,
+ allowsOptimization,
+ allowsObfuscation,
includeDescriptorClasses);
}
}
+ public final boolean allowsAccessModification;
public final boolean allowsShrinking;
public final boolean allowsOptimization;
public final boolean allowsObfuscation;
public final boolean includeDescriptorClasses;
private ProguardKeepRuleModifiers(
+ boolean allowsAccessModification,
boolean allowsShrinking,
boolean allowsOptimization,
boolean allowsObfuscation,
boolean includeDescriptorClasses) {
+ this.allowsAccessModification = allowsAccessModification;
this.allowsShrinking = allowsShrinking;
this.allowsOptimization = allowsOptimization;
this.allowsObfuscation = allowsObfuscation;
this.includeDescriptorClasses = includeDescriptorClasses;
}
+
/**
* Create a new empty builder.
*/
@@ -64,8 +78,8 @@
return false;
}
ProguardKeepRuleModifiers that = (ProguardKeepRuleModifiers) o;
-
- return allowsShrinking == that.allowsShrinking
+ return allowsAccessModification == that.allowsAccessModification
+ && allowsShrinking == that.allowsShrinking
&& allowsOptimization == that.allowsOptimization
&& allowsObfuscation == that.allowsObfuscation
&& includeDescriptorClasses == that.includeDescriptorClasses;
@@ -73,15 +87,17 @@
@Override
public int hashCode() {
- return (allowsShrinking ? 1 : 0)
- | (allowsOptimization ? 2 : 0)
- | (allowsObfuscation ? 4 : 0)
- | (includeDescriptorClasses ? 8 : 0);
+ return (allowsAccessModification ? 1 : 0)
+ | (allowsShrinking ? 2 : 0)
+ | (allowsOptimization ? 4 : 0)
+ | (allowsObfuscation ? 8 : 0)
+ | (includeDescriptorClasses ? 16 : 0);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
+ appendWithComma(builder, allowsAccessModification, "allowaccessmodification");
appendWithComma(builder, allowsObfuscation, "allowobfuscation");
appendWithComma(builder, allowsShrinking, "allowshrinking");
appendWithComma(builder, allowsOptimization, "allowoptimization");
@@ -89,8 +105,7 @@
return builder.toString();
}
- private void appendWithComma(StringBuilder builder, boolean predicate,
- String text) {
+ private void appendWithComma(StringBuilder builder, boolean predicate, String text) {
if (!predicate) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6560ebc..346caa4 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -109,7 +109,12 @@
Log.debug(getClass(), "Removing class: " + clazz);
}
prunedTypes.add(clazz.type);
- usagePrinter.printUnusedClass(clazz);
+ // TODO(b/150118654): It would be nice to add something such as
+ // clazz.type.isD8R8SynthesizedType, but such test is currently expensive since it is
+ // based on strings, so we check only against the enum unboxing utility class.
+ if (clazz.type != appView.dexItemFactory().enumUnboxingUtilityType) {
+ usagePrinter.printUnusedClass(clazz);
+ }
}
}
return newClasses;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index a540236..da9038d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -52,6 +52,7 @@
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.base.Equivalence;
@@ -946,7 +947,7 @@
for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
DexEncodedMethod shadowedBy = findMethodInTarget(virtualMethod);
if (shadowedBy != null) {
- if (virtualMethod.accessFlags.isAbstract()) {
+ if (virtualMethod.isAbstract()) {
// Remove abstract/interface methods that are shadowed.
deferredRenamings.map(virtualMethod.method, shadowedBy.method);
@@ -968,7 +969,7 @@
// The method is not shadowed. If it is abstract, we can simply move it to the subclass.
// Non-abstract methods are handled below (they cannot simply be moved to the subclass as
// a virtual method, because they might be the target of an invoke-super instruction).
- if (virtualMethod.accessFlags.isAbstract()) {
+ if (virtualMethod.isAbstract()) {
// Abort if target is non-abstract and does not override the abstract method.
if (!target.isAbstract()) {
assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
@@ -978,6 +979,8 @@
// Update the holder of [virtualMethod] using renameMethod().
DexEncodedMethod resultingVirtualMethod =
renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
+ resultingVirtualMethod.setLibraryMethodOverride(
+ virtualMethod.isLibraryMethodOverride());
deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
@@ -1251,6 +1254,7 @@
code,
method.hasClassFileVersion() ? method.getClassFileVersion() : -1,
true);
+ bridge.setLibraryMethodOverride(method.isLibraryMethodOverride());
if (method.accessFlags.isPromotedToPublic()) {
// The bridge is now the public method serving the role of the original method, and should
// reflect that this method was publicized.
@@ -1472,16 +1476,25 @@
return lens;
}
- private DexEncodedMethod fixupMethod(DexEncodedMethod encodedMethod) {
- DexMethod method = encodedMethod.method;
- DexMethod newMethod = fixupMethod(method);
- if (newMethod != method) {
- if (!lensBuilder.hasOriginalSignatureMappingFor(newMethod)) {
- lensBuilder.map(method, newMethod).recordMove(method, newMethod);
+ private DexEncodedMethod fixupMethod(DexEncodedMethod method) {
+ DexMethod methodReference = method.method;
+ DexMethod newMethodReference = fixupMethod(methodReference);
+ if (newMethodReference != methodReference) {
+ if (!lensBuilder.hasOriginalSignatureMappingFor(newMethodReference)) {
+ lensBuilder
+ .map(methodReference, newMethodReference)
+ .recordMove(methodReference, newMethodReference);
}
- return encodedMethod.toTypeSubstitutedMethod(newMethod);
+ DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(newMethodReference);
+ if (newMethod.isNonPrivateVirtualMethod()) {
+ // Since we changed the return type or one of the parameters, this method cannot be a
+ // classpath or library method override, since we only class merge program classes.
+ assert !method.isLibraryMethodOverride().isTrue();
+ newMethod.setLibraryMethodOverride(OptionalBool.FALSE);
+ }
+ return newMethod;
}
- return encodedMethod;
+ return method;
}
private void fixupFields(List<DexEncodedField> fields, FieldSetter setter) {
@@ -1714,7 +1727,7 @@
}
@Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLense applied) {
throw new Unreachable();
}
diff --git a/src/main/java/com/android/tools/r8/utils/AbortException.java b/src/main/java/com/android/tools/r8/utils/AbortException.java
index 0253dfa..c1e4cd2 100644
--- a/src/main/java/com/android/tools/r8/utils/AbortException.java
+++ b/src/main/java/com/android/tools/r8/utils/AbortException.java
@@ -3,17 +3,43 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
/**
- * Exception thrown to interrupt processing after a fatal error. The exception doesn't carry
- * directly information about the fatal error: the problem was already reported to the
- * {@link com.android.tools.r8.DiagnosticsHandler}.
+ * Exception thrown to interrupt processing after a fatal error or fail-if-error barrier.
+ *
+ * <p>The abort always contains the diagnostic causing the fatal error, or in the case of multiple
+ * pending errors, one of the errors. In all cases, the abort exception signifies that the error has
+ * been reported to the {@link com.android.tools.r8.DiagnosticsHandler}.
*/
public class AbortException extends RuntimeException {
- public AbortException() {
+ private final Diagnostic diagnostic;
+ public AbortException(Diagnostic diagnostic) {
+ assert diagnostic != null;
+ this.diagnostic = diagnostic;
}
- public AbortException(String message) {
- super(message);
+ @Override
+ public synchronized Throwable getCause() {
+ // In the case of exception diagnostics, treat that as the parent cause.
+ return diagnostic instanceof ExceptionDiagnostic
+ ? ((ExceptionDiagnostic) diagnostic).getCause()
+ : null;
+ }
+
+ @Override
+ public String getMessage() {
+ return diagnostic.getDiagnosticMessage();
+ }
+
+ public Origin getOrigin() {
+ return diagnostic != null ? diagnostic.getOrigin() : Origin.unknown();
+ }
+
+ public Position getPosition() {
+ return diagnostic != null ? diagnostic.getPosition() : Position.UNKNOWN;
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 8dd1b92..00853e0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DirectoryClassFileProvider;
+import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
@@ -31,11 +32,11 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.FeatureSplitConfiguration;
import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.shaking.ProguardConfiguration;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -45,6 +46,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -57,11 +59,13 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -75,11 +79,25 @@
public class AndroidApp {
private static final String dumpVersionFileName = "r8-version";
+ private static final String dumpBuildPropertiesFileName = "build.properties";
private static final String dumpProgramFileName = "program.jar";
private static final String dumpClasspathFileName = "classpath.jar";
private static final String dumpLibraryFileName = "library.jar";
private static final String dumpConfigFileName = "proguard.config";
+ private static Map<FeatureSplit, String> dumpFeatureSplitFileNames(
+ FeatureSplitConfiguration featureSplitConfiguration) {
+ Map<FeatureSplit, String> featureSplitFileNames = new IdentityHashMap<>();
+ if (featureSplitConfiguration != null) {
+ int i = 1;
+ for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ featureSplitFileNames.put(featureSplit, "feature-" + i + ".jar");
+ i++;
+ }
+ }
+ return featureSplitFileNames;
+ }
+
private final ImmutableList<ProgramResourceProvider> programResourceProviders;
private final ImmutableMap<Resource, String> programResourcesMainDescriptor;
private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
@@ -422,25 +440,40 @@
return programResourcesMainDescriptor.get(resource);
}
- public void dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
+ public void dump(Path output, InternalOptions options) {
int nextDexIndex = 0;
OpenOption[] openOptions =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
writeToZipStream(
out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
- if (configuration != null) {
- String proguardConfig = configuration.getParsedConfiguration();
+ writeToZipStream(
+ out,
+ dumpBuildPropertiesFileName,
+ getBuildPropertiesContents(options).getBytes(),
+ ZipEntry.DEFLATED);
+ if (options.getProguardConfiguration() != null) {
+ String proguardConfig = options.getProguardConfiguration().getParsedConfiguration();
writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
}
- nextDexIndex = dumpProgramResources(dumpProgramFileName, nextDexIndex, out);
+ nextDexIndex =
+ dumpProgramResources(
+ dumpProgramFileName,
+ dumpFeatureSplitFileNames(options.featureSplitConfiguration),
+ nextDexIndex,
+ out,
+ options.featureSplitConfiguration);
nextDexIndex = dumpClasspathResources(nextDexIndex, out);
nextDexIndex = dumpLibraryResources(nextDexIndex, out);
} catch (IOException | ResourceException e) {
- reporter.fatalError(new StringDiagnostic("Failed to dump inputs"), e);
+ throw options.reporter.fatalError(new ExceptionDiagnostic(e));
}
}
+ private String getBuildPropertiesContents(InternalOptions options) {
+ return "min-api=" + options.minApiLevel;
+ }
+
private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out)
throws IOException, ResourceException {
nextDexIndex =
@@ -480,31 +513,96 @@
};
}
- private int dumpProgramResources(String archiveName, int nextDexIndex, ZipOutputStream out)
+ private int dumpProgramResources(
+ String archiveName,
+ Map<FeatureSplit, String> featureSplitArchiveNames,
+ int nextDexIndex,
+ ZipOutputStream out,
+ FeatureSplitConfiguration featureSplitConfiguration)
throws IOException, ResourceException {
- try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
- try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
- Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
- Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
- for (DataEntryResource dataResource : dataEntries) {
- String entryName = dataResource.getName();
- try (InputStream dataStream = dataResource.getByteStream()) {
- byte[] bytes = ByteStreams.toByteArray(dataStream);
- writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+ Map<FeatureSplit, ByteArrayOutputStream> featureSplitArchiveByteStreams =
+ new IdentityHashMap<>();
+ Map<FeatureSplit, ZipOutputStream> featureSplitArchiveOutputStreams = new IdentityHashMap<>();
+ try {
+ if (featureSplitConfiguration != null) {
+ for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();
+ featureSplitArchiveByteStreams.put(featureSplit, archiveByteStream);
+ featureSplitArchiveOutputStreams.put(
+ featureSplit, new ZipOutputStream(archiveByteStream));
+ }
+ }
+ try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+ try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+ Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
+ Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
+ for (DataEntryResource dataResource : dataEntries) {
+ String entryName = dataResource.getName();
+ try (InputStream dataStream = dataResource.getByteStream()) {
+ byte[] bytes = ByteStreams.toByteArray(dataStream);
+ writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+ }
+ }
+ for (ProgramResourceProvider provider : programResourceProviders) {
+ for (ProgramResource programResource : provider.getProgramResources()) {
+ nextDexIndex =
+ dumpProgramResource(
+ seen,
+ nextDexIndex,
+ classDescriptor -> {
+ if (featureSplitConfiguration != null) {
+ FeatureSplit featureSplit =
+ featureSplitConfiguration.getFeatureSplitFromClassDescriptor(
+ classDescriptor);
+ if (featureSplit != null) {
+ return featureSplitArchiveOutputStreams.get(featureSplit);
+ }
+ }
+ return archiveOutputStream;
+ },
+ archiveOutputStream,
+ programResource);
+ }
}
}
- for (ProgramResourceProvider provider : programResourceProviders) {
- for (ProgramResource programResource : provider.getProgramResources()) {
- nextDexIndex =
- dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+ writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+ if (featureSplitConfiguration != null) {
+ for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+ featureSplitArchiveOutputStreams.remove(featureSplit).close();
+ writeToZipStream(
+ out,
+ featureSplitArchiveNames.get(featureSplit),
+ featureSplitArchiveByteStreams.get(featureSplit).toByteArray(),
+ ZipEntry.DEFLATED);
}
}
}
- writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+ } finally {
+ closeOutputStreams(featureSplitArchiveOutputStreams.values());
}
return nextDexIndex;
}
+ private void closeOutputStreams(Collection<ZipOutputStream> outputStreams) throws IOException {
+ IOException exception = null;
+ RuntimeException runtimeException = null;
+ for (OutputStream outputStream : outputStreams) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ exception = e;
+ } catch (RuntimeException e) {
+ runtimeException = e;
+ }
+ }
+ if (exception != null) {
+ throw exception;
+ }
+ if (runtimeException != null) {
+ throw runtimeException;
+ }
+ }
+
private static int dumpClassFileResources(
String archiveName,
int nextDexIndex,
@@ -519,7 +617,12 @@
ProgramResource programResource = provider.getProgramResource(descriptor);
int oldDexIndex = nextDexIndex;
nextDexIndex =
- dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+ dumpProgramResource(
+ seen,
+ nextDexIndex,
+ ignore -> archiveOutputStream,
+ archiveOutputStream,
+ programResource);
assert nextDexIndex == oldDexIndex;
}
}
@@ -532,11 +635,11 @@
private static int dumpProgramResource(
Object2IntMap<String> seen,
int nextDexIndex,
- ZipOutputStream archiveOutputStream,
+ Function<String, ZipOutputStream> cfArchiveOutputStream,
+ ZipOutputStream dexArchiveOutputStream,
ProgramResource programResource)
throws ResourceException, IOException {
byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
- String entryName;
if (programResource.getKind() == Kind.CF) {
Set<String> classDescriptors = programResource.getClassDescriptors();
String classDescriptor =
@@ -546,12 +649,14 @@
String classFileName = DescriptorUtils.getClassFileName(classDescriptor);
int dupCount = seen.getOrDefault(classDescriptor, 0);
seen.put(classDescriptor, dupCount + 1);
- entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+ String entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+ writeToZipStream(
+ cfArchiveOutputStream.apply(classDescriptor), entryName, bytes, ZipEntry.DEFLATED);
} else {
assert programResource.getKind() == Kind.DEX;
- entryName = "classes" + nextDexIndex++ + ".dex";
+ String entryName = "classes" + nextDexIndex++ + ".dex";
+ writeToZipStream(dexArchiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
}
- writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
return nextDexIndex;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index a47684f..dfb625b 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -92,12 +92,13 @@
}
private void handleIOException(IOException e, DiagnosticsHandler handler) {
+ ExceptionDiagnostic diagnostic = new ExceptionDiagnostic(e, origin);
if (e instanceof ZipException && e.getMessage().startsWith("duplicate entry")) {
// For now we stick to the Proguard behaviour, see section "Warning: can't write resource ...
// Duplicate zip entry" on https://www.guardsquare.com/en/proguard/manual/troubleshooting.
- handler.warning(new ExceptionDiagnostic(e, origin));
+ handler.warning(diagnostic);
} else {
- handler.error(new ExceptionDiagnostic(e, origin));
+ handler.error(diagnostic);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
index 8d3ec1e..8938bc8 100644
--- a/src/main/java/com/android/tools/r8/utils/Box.java
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.utils;
+import java.util.function.Supplier;
+
public class Box<T> {
private T value;
@@ -14,6 +16,13 @@
set(initialValue);
}
+ public T computeIfAbsent(Supplier<T> supplier) {
+ if (value == null) {
+ value = supplier.get();
+ }
+ return value;
+ }
+
public T get() {
return value;
}
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 21a5123..38cd73e 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -3,9 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
import com.android.tools.r8.CompatProguardCommandBuilder;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command.Builder;
@@ -13,7 +16,9 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/**
* Wrapper to make it easy to call R8 in compat mode when compiling a dump file.
@@ -30,7 +35,7 @@
private static final List<String> VALID_OPTIONS =
Arrays.asList("--classfile", "--compat", "--debug", "--release");
- private static final List<String> VALID_OPTIONS_WITH_OPERAND =
+ private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
Arrays.asList(
"--output",
"--lib",
@@ -43,6 +48,9 @@
"--pg-map-output",
"--desugared-lib");
+ private static final List<String> VALID_OPTIONS_WITH_TWO_OPERANDS =
+ Arrays.asList("--feature-jar");
+
public static void main(String[] args) throws CompilationFailedException {
boolean isCompatMode = false;
OutputMode outputMode = OutputMode.DexIndexed;
@@ -50,6 +58,7 @@
Path pgMapOutput = null;
CompilationMode compilationMode = CompilationMode.RELEASE;
List<Path> program = new ArrayList<>();
+ Map<Path, Path> features = new LinkedHashMap<>();
List<Path> library = new ArrayList<>();
List<Path> classpath = new ArrayList<>();
List<Path> config = new ArrayList<>();
@@ -81,7 +90,7 @@
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
- } else if (VALID_OPTIONS_WITH_OPERAND.contains(option)) {
+ } else if (VALID_OPTIONS_WITH_SINGLE_OPERAND.contains(option)) {
String operand = args[++i];
switch (option) {
case "--output":
@@ -117,11 +126,29 @@
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
+ } else if (VALID_OPTIONS_WITH_TWO_OPERANDS.contains(option)) {
+ String firstOperand = args[++i];
+ String secondOperand = args[++i];
+ switch (option) {
+ case "--feature-jar":
+ {
+ Path featureIn = Paths.get(firstOperand);
+ Path featureOut = Paths.get(secondOperand);
+ if (!isArchive(featureIn)) {
+ throw new IllegalArgumentException(
+ "Expected an archive, got `" + featureIn.toString() + "`.");
+ }
+ features.put(featureIn, featureOut);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unimplemented option: " + option);
+ }
} else {
program.add(Paths.get(option));
}
}
- Builder builder =
+ Builder commandBuilder =
new CompatProguardCommandBuilder(isCompatMode)
.addProgramFiles(program)
.addLibraryFiles(library)
@@ -130,9 +157,17 @@
.setOutput(outputPath, outputMode)
.setMode(compilationMode)
.setMinApiLevel(minApi);
+ features.forEach(
+ (in, out) ->
+ commandBuilder.addFeatureSplit(
+ featureBuilder ->
+ featureBuilder
+ .addProgramResourceProvider(ArchiveResourceProvider.fromArchive(in, true))
+ .setProgramConsumer(new ArchiveConsumer(out))
+ .build()));
if (pgMapOutput != null) {
- builder.setProguardMapOutputPath(pgMapOutput);
+ commandBuilder.setProguardMapOutputPath(pgMapOutput);
}
- R8.run(builder.build());
+ R8.run(commandBuilder.build());
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
index 0979b27..2079af2 100644
--- a/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ConsumerUtils.java
@@ -4,8 +4,14 @@
package com.android.tools.r8.utils;
+import java.util.function.Consumer;
+
public class ConsumerUtils {
+ public static <T> Consumer<T> emptyConsumer() {
+ return ignore -> {};
+ }
+
public static <T> ThrowingConsumer<T, RuntimeException> emptyThrowingConsumer() {
return ignore -> {};
}
diff --git a/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java b/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
deleted file mode 100644
index b169297..0000000
--- a/src/main/java/com/android/tools/r8/utils/DiagnosticWithThrowable.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2017, 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 com.android.tools.r8.Diagnostic;
-
-public abstract class DiagnosticWithThrowable implements Diagnostic {
-
- private final Throwable throwable;
-
- protected DiagnosticWithThrowable(Throwable throwable) {
- assert throwable != null;
- this.throwable = throwable;
- }
-
- public Throwable getThrowable() {
- return throwable;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 4dc6d48..a73aeb5 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -4,28 +4,40 @@
package com.android.tools.r8.utils;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.Keep;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.NoSuchFileException;
+/**
+ * Diagnostic for any unhandled exception arising during compilation.
+ *
+ * <p>The inner-most exception giving rise to the exception can be obtained as the "cause". If the
+ * the inner-most exception is not the same as the exception at the point of the interception and
+ * conversion to a diagnostic, the full exception stack can be obtained in the suppressed exceptions
+ * on the inner-most cause.
+ */
@Keep
-public class ExceptionDiagnostic extends DiagnosticWithThrowable {
+public class ExceptionDiagnostic implements Diagnostic {
+ private final Throwable cause;
private final Origin origin;
private final Position position;
- public ExceptionDiagnostic(Throwable e, Origin origin, Position position) {
- super(e);
+ public ExceptionDiagnostic(Throwable cause, Origin origin, Position position) {
+ assert cause != null;
+ assert origin != null;
+ assert position != null;
+ this.cause = cause;
this.origin = origin;
this.position = position;
}
+ public ExceptionDiagnostic(Throwable cause) {
+ this(cause, Origin.unknown(), Position.UNKNOWN);
+ }
+
public ExceptionDiagnostic(Throwable e, Origin origin) {
this(e, origin, Position.UNKNOWN);
}
@@ -44,20 +56,12 @@
return position;
}
+ public Throwable getCause() {
+ return cause;
+ }
+
@Override
public String getDiagnosticMessage() {
- Throwable e = getThrowable();
- if (e instanceof NoSuchFileException || e instanceof FileNotFoundException) {
- return "File not found: " + e.getMessage();
- }
- if (e instanceof FileAlreadyExistsException) {
- return "File already exists: " + e.getMessage();
- }
- StringWriter stack = new StringWriter();
- e.printStackTrace(new PrintWriter(stack));
- String message = e.getMessage();
- return message != null
- ? StringUtils.joinLines(message, "Stack trace:", stack.toString())
- : StringUtils.joinLines(stack.toString());
+ return cause.toString();
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 79e3818..e5ba3f1 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -12,10 +12,11 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.position.Position;
-import com.google.common.collect.ObjectArrays;
import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -65,30 +66,95 @@
public static void withCompilationHandler(Reporter reporter, CompileAction action)
throws CompilationFailedException {
try {
- try {
- action.run();
- } catch (IOException e) {
- throw reporter.fatalError(new ExceptionDiagnostic(e, extractIOExceptionOrigin(e)));
- } catch (CompilationError e) {
- throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin(), e.getPosition()));
- } catch (ResourceException e) {
- throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
- } catch (AssertionError e) {
- throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()));
- } catch (Exception e) {
- String filename = "Version_" + Version.LABEL + ".java";
- StackTraceElement versionElement = new StackTraceElement(
- Version.class.getSimpleName(), "fakeStackEntry", filename, 0);
- StackTraceElement[] withVersion = ObjectArrays.concat(versionElement, e.getStackTrace());
- e.setStackTrace(withVersion);
- throw e;
- }
+ action.run();
reporter.failIfPendingErrors();
- } catch (AbortException e) {
- throw new CompilationFailedException(e);
+ } catch (Throwable e) {
+ throw failCompilation(reporter, e);
}
}
+ private static CompilationFailedException failCompilation(
+ Reporter reporter, Throwable topMostException) {
+ // Find inner-most cause of the failure and compute origin, position and reported for the path.
+ boolean hasBeenReported = false;
+ Origin origin = Origin.unknown();
+ Position position = Position.UNKNOWN;
+ List<Throwable> suppressed = new ArrayList<>();
+ Throwable innerMostCause = topMostException;
+ while (true) {
+ hasBeenReported |= innerMostCause instanceof AbortException;
+ Origin nextOrigin = getOrigin(innerMostCause);
+ if (nextOrigin != Origin.unknown()) {
+ origin = nextOrigin;
+ }
+ Position nextPosition = getPosition(innerMostCause);
+ if (nextPosition != Position.UNKNOWN) {
+ position = nextPosition;
+ }
+ if (innerMostCause.getCause() == null || suppressed.contains(innerMostCause)) {
+ break;
+ }
+ suppressed.add(innerMostCause);
+ innerMostCause = innerMostCause.getCause();
+ }
+
+ // If no abort is seen, the exception is not reported, so report it now.
+ if (!hasBeenReported) {
+ reporter.error(new ExceptionDiagnostic(innerMostCause, origin, position));
+ }
+
+ // Build the top-level compiler exception and version stack.
+ StringBuilder message = new StringBuilder("Compilation failed to complete");
+ if (position != Position.UNKNOWN) {
+ message.append(", position: ").append(position);
+ }
+ if (origin != Origin.unknown()) {
+ message.append(", origin: ").append(origin);
+ }
+ // Create the final exception object.
+ CompilationFailedException rethrow =
+ new CompilationFailedException(message.toString(), innerMostCause);
+ // Replace its stack by the cause stack and insert version info at the top.
+ String filename = "Version_" + Version.LABEL + ".java";
+ rethrow.setStackTrace(
+ new StackTraceElement[] {
+ new StackTraceElement(Version.class.getSimpleName(), "fakeStackEntry", filename, 0)
+ });
+ return rethrow;
+ }
+
+ private static Origin getOrigin(Throwable e) {
+ if (e instanceof IOException) {
+ return extractIOExceptionOrigin((IOException) e);
+ }
+ if (e instanceof CompilationError) {
+ return ((CompilationError) e).getOrigin();
+ }
+ if (e instanceof ResourceException) {
+ return ((ResourceException) e).getOrigin();
+ }
+ if (e instanceof OriginAttachmentException) {
+ return ((OriginAttachmentException) e).origin;
+ }
+ if (e instanceof AbortException) {
+ return ((AbortException) e).getOrigin();
+ }
+ return Origin.unknown();
+ }
+
+ private static Position getPosition(Throwable e) {
+ if (e instanceof CompilationError) {
+ return ((CompilationError) e).getPosition();
+ }
+ if (e instanceof OriginAttachmentException) {
+ return ((OriginAttachmentException) e).position;
+ }
+ if (e instanceof AbortException) {
+ return ((AbortException) e).getPosition();
+ }
+ return Position.UNKNOWN;
+ }
+
public interface MainAction {
void run() throws CompilationFailedException;
}
@@ -111,7 +177,7 @@
// We should try to avoid the use of this extraction as it signifies a point where we don't have
// enough context to associate a specific origin with an IOException. Concretely, we should move
// towards always catching IOException and rethrowing CompilationError with proper origins.
- public static Origin extractIOExceptionOrigin(IOException e) {
+ private static Origin extractIOExceptionOrigin(IOException e) {
if (e instanceof FileSystemException) {
FileSystemException fse = (FileSystemException) e;
if (fse.getFile() != null && !fse.getFile().isEmpty()) {
@@ -122,27 +188,60 @@
}
public static RuntimeException unwrapExecutionException(ExecutionException executionException) {
- Throwable cause = executionException.getCause();
- if (cause instanceof Error) {
- // add original exception as suppressed exception to provide the original stack trace
- cause.addSuppressed(executionException);
- throw (Error) cause;
- } else if (cause instanceof RuntimeException) {
- cause.addSuppressed(executionException);
- throw (RuntimeException) cause;
- } else {
- throw new RuntimeException(executionException.getMessage(), cause);
- }
+ return new RuntimeException(executionException);
}
- public static <T> T withOriginAttachmentHandler(
+ public static void withOriginAttachmentHandler(Origin origin, Runnable action) {
+ withOriginAndPositionAttachmentHandler(origin, Position.UNKNOWN, action);
+ }
+
+ public static <T> T withOriginAttachmentHandler(Origin origin, Supplier<T> action) {
+ return withOriginAndPositionAttachmentHandler(origin, Position.UNKNOWN, action);
+ }
+
+ public static void withOriginAndPositionAttachmentHandler(
+ Origin origin, Position position, Runnable action) {
+ withOriginAndPositionAttachmentHandler(
+ origin,
+ position,
+ () -> {
+ action.run();
+ return null;
+ });
+ }
+
+ public static <T> T withOriginAndPositionAttachmentHandler(
Origin origin, Position position, Supplier<T> action) {
try {
return action.get();
- } catch (CompilationError e) {
- throw e.withAdditionalOriginAndPositionInfo(origin, position);
} catch (RuntimeException e) {
- throw new CompilationError(e.getMessage(), e, origin, position);
+ throw OriginAttachmentException.wrap(e, origin, position);
+ }
+ }
+
+ private static class OriginAttachmentException extends RuntimeException {
+ final Origin origin;
+ final Position position;
+
+ public static RuntimeException wrap(RuntimeException e, Origin origin, Position position) {
+ return needsAttachment(e, origin, position)
+ ? new OriginAttachmentException(e, origin, position)
+ : e;
+ }
+
+ private OriginAttachmentException(RuntimeException e, Origin origin, Position position) {
+ super(e);
+ this.origin = origin;
+ this.position = position;
+ }
+
+ private static boolean needsAttachment(RuntimeException e, Origin origin, Position position) {
+ if (origin == Origin.unknown() && position == Position.UNKNOWN) {
+ return false;
+ }
+ Origin existingOrigin = getOrigin(e);
+ Position existingPosition = getPosition(e);
+ return origin != existingOrigin || position != existingPosition;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 0230ec1..00412d9 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -101,8 +101,9 @@
try {
lines = FileUtils.readAllLines(file);
} catch (IOException e) {
- reporter.error(new ExceptionDiagnostic(e, new SpecificationOrigin(file)));
- throw new AbortException();
+ ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file));
+ reporter.error(error);
+ throw new AbortException(error);
}
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
@@ -120,8 +121,9 @@
.map(DescriptorUtils::descriptorToJavaType)
.collect(Collectors.toList());
} catch (IOException e) {
- reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
- throw new AbortException();
+ ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath));
+ reporter.error(error);
+ throw new AbortException(error);
}
}
@@ -132,8 +134,9 @@
.map(ZipEntry::getName)
.collect(Collectors.toList());
} catch (IOException e) {
- reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar))));
- throw new AbortException();
+ ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar)));
+ reporter.error(error);
+ throw new AbortException(error);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6b1fb12..997d4e9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -195,8 +195,7 @@
enableValuePropagation = false;
enableSideEffectAnalysis = false;
enableTreeShakingOfLibraryMethodOverrides = false;
- enablePropagationOfDynamicTypesAtCallSites = false;
- enablePropagationOfConstantsAtCallSites = false;
+ callSiteOptimizationOptions.disableOptimization();
}
public boolean printTimes = System.getProperty("com.android.tools.r8.printtimes") != null;
@@ -247,14 +246,6 @@
public boolean enableNameReflectionOptimization = true;
public boolean enableStringConcatenationOptimization = true;
public boolean enableTreeShakingOfLibraryMethodOverrides = false;
- public boolean enablePropagationOfDynamicTypesAtCallSites = true;
- // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
- public boolean enablePropagationOfConstantsAtCallSites = false;
- // At 2.0, part of @Metadata up to this flag is rewritten, which is super-type hierarchy.
- public boolean enableKotlinMetadataRewritingForMembers = true;
- // Up to 2.0, Kotlin @Metadata is removed if the associated class is renamed.
- // Under this flag, Kotlin @Metadata is generally kept and modified for all program classes.
- public boolean enableKotlinMetadataRewritingForRenamedClasses = true;
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
public boolean cfToCfDesugar = false;
@@ -308,6 +299,9 @@
// 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;
+
// Hidden marker for classes.dex
private boolean hasMarker = false;
private Marker marker;
@@ -488,6 +482,9 @@
// to disable the check that the build makes sense for multi-dexing.
public boolean enableMainDexListCheck = true;
+ // TODO(b/156934674): Remove when resource shrinker is removed.
+ public boolean isRunningDeprecatedResourceShrinker = false;
+
private final boolean enableTreeShaking;
private final boolean enableMinification;
@@ -517,6 +514,8 @@
public boolean debug = false;
+ private final CallSiteOptimizationOptions callSiteOptimizationOptions =
+ new CallSiteOptimizationOptions();
private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
private final KotlinOptimizationOptions kotlinOptimizationOptions =
new KotlinOptimizationOptions();
@@ -535,6 +534,10 @@
public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
+ public CallSiteOptimizationOptions callSiteOptimizationOptions() {
+ return callSiteOptimizationOptions;
+ }
+
public ProtoShrinkingOptions protoShrinking() {
return protoShrinking;
}
@@ -1065,6 +1068,51 @@
System.getProperty("com.android.tools.r8.disableKotlinSpecificOptimizations") != null;
}
+ public static class CallSiteOptimizationOptions {
+
+ // Each time we see an invoke with more dispatch targets than the threshold, we stop call site
+ // propagation for all these dispatch targets. The motivation for this is that it is expensive
+ // and that we are somewhat unlikely to have precise knowledge about the value of arguments when
+ // there are many (possibly spurious) call graph edges.
+ private final int maxNumberOfDispatchTargetsBeforeAbandoning = 10;
+
+ // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
+ private boolean enableConstantPropagation = false;
+ private boolean enableTypePropagation = true;
+
+ private void disableOptimization() {
+ enableConstantPropagation = false;
+ enableTypePropagation = false;
+ }
+
+ public void disableTypePropagationForTesting() {
+ enableTypePropagation = false;
+ }
+
+ // TODO(b/69963623): Remove this once enabled.
+ @VisibleForTesting
+ public static void enableConstantPropagationForTesting(InternalOptions options) {
+ assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
+ options.callSiteOptimizationOptions().enableConstantPropagation = true;
+ }
+
+ public int getMaxNumberOfDispatchTargetsBeforeAbandoning() {
+ return maxNumberOfDispatchTargetsBeforeAbandoning;
+ }
+
+ public boolean isEnabled() {
+ return enableConstantPropagation || enableTypePropagation;
+ }
+
+ public boolean isConstantPropagationEnabled() {
+ return enableConstantPropagation;
+ }
+
+ public boolean isTypePropagationEnabled() {
+ return enableTypePropagation;
+ }
+ }
+
public static class ProtoShrinkingOptions {
public boolean enableGeneratedExtensionRegistryShrinking = false;
@@ -1128,7 +1176,6 @@
public boolean enableSwitchToIfRewriting = true;
public boolean enableEnumUnboxingDebugLogs = false;
public boolean forceRedundantConstNumberRemoval = false;
- public boolean forceAssumeNoneInsertion = false;
public boolean invertConditionals = false;
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
@@ -1166,6 +1213,9 @@
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;
@@ -1191,7 +1241,7 @@
public Consumer<ProgramMethod> callSiteOptimizationInfoInspector = null;
- public Predicate<DexEncodedMethod> cfByteCodePassThrough = null;
+ public Predicate<DexMethod> cfByteCodePassThrough = null;
}
@VisibleForTesting
@@ -1201,22 +1251,6 @@
enableNameReflectionOptimization = false;
}
- // TODO(b/69963623): Remove this once enabled.
- @VisibleForTesting
- public void enablePropagationOfConstantsAtCallSites() {
- assert !enablePropagationOfConstantsAtCallSites;
- enablePropagationOfConstantsAtCallSites = true;
- }
-
- public boolean isCallSiteOptimizationEnabled() {
- return enablePropagationOfConstantsAtCallSites || enablePropagationOfDynamicTypesAtCallSites;
- }
-
- public void disableCallSiteOptimization() {
- enablePropagationOfConstantsAtCallSites = false;
- enablePropagationOfDynamicTypesAtCallSites = false;
- }
-
private boolean hasMinApi(AndroidApiLevel level) {
assert isGeneratingDex();
return minApiLevel >= level.getLevel();
diff --git a/src/main/java/com/android/tools/r8/utils/LazyBox.java b/src/main/java/com/android/tools/r8/utils/LazyBox.java
new file mode 100644
index 0000000..38d1f2a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LazyBox.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, 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 java.util.function.Supplier;
+
+public class LazyBox<T> extends Box<T> {
+
+ private final Supplier<T> supplier;
+
+ public LazyBox(Supplier<T> supplier) {
+ this.supplier = supplier;
+ }
+
+ public T computeIfAbsent() {
+ return super.computeIfAbsent(supplier);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index ef17f80..f7f9d1f 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -3,22 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import java.util.ArrayList;
-import java.util.Collection;
public class Reporter implements DiagnosticsHandler {
private final DiagnosticsHandler clientHandler;
- private int errorCount = 0;
- private Diagnostic lastError;
- private final Collection<Throwable> suppressedExceptions = new ArrayList<>();
+ private AbortException abort = null;
public Reporter() {
this(new DiagnosticsHandler() {});
@@ -44,28 +36,19 @@
@Override
public synchronized void error(Diagnostic error) {
+ abort = new AbortException(error);
clientHandler.error(error);
- lastError = error;
- errorCount++;
}
public void error(String message) {
error(new StringDiagnostic(message));
}
- public synchronized void error(Diagnostic error, Throwable suppressedException) {
- clientHandler.error(error);
- lastError = error;
- errorCount++;
- suppressedExceptions.add(suppressedException);
- }
-
/**
* @throws AbortException always.
*/
public RuntimeException fatalError(String message) {
- fatalError(new StringDiagnostic(message));
- throw new Unreachable();
+ throw fatalError(new StringDiagnostic(message));
}
/**
@@ -77,53 +60,10 @@
throw new Unreachable();
}
- /**
- * @throws AbortException always.
- */
- public RuntimeException fatalError(Diagnostic error, Throwable suppressedException) {
- error(error, suppressedException);
- failIfPendingErrors();
- throw new Unreachable();
- }
-
- /**
- * @throws AbortException if any error was reported.
- */
- public void failIfPendingErrors() {
- synchronized (this) {
- if (errorCount != 0) {
- AbortException abort;
- if (lastError != null && lastError.getDiagnosticMessage() != null) {
- StringBuilder builder = new StringBuilder("Error: ");
- if (lastError.getOrigin() != Origin.unknown()) {
- builder.append(lastError.getOrigin()).append(", ");
- }
- if (lastError.getPosition() != Position.UNKNOWN) {
- builder.append(lastError.getPosition()).append(", ");
- }
- builder.append(lastError.getDiagnosticMessage());
- abort = new AbortException(builder.toString());
- } else {
- abort = new AbortException();
- }
- throw addSuppressedExceptions(abort);
- }
- }
- }
-
- private <T extends Throwable> T addSuppressedExceptions(T t) {
- suppressedExceptions.forEach(t::addSuppressed);
- return t;
- }
-
- public void guard(Runnable action) throws CompilationFailedException {
- try {
- action.run();
- } catch (CompilationError e) {
- error(e.toStringDiagnostic());
- throw addSuppressedExceptions(new CompilationFailedException());
- } catch (AbortException e) {
- throw new CompilationFailedException(e);
+ /** @throws AbortException if any error was reported. */
+ public synchronized void failIfPendingErrors() {
+ if (abort != null) {
+ throw abort;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
index 7b482a8..6476a5a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/StringDiagnostic.java
@@ -33,6 +33,9 @@
}
public StringDiagnostic(String message, Origin origin, Position position) {
+ assert message != null;
+ assert origin != null;
+ assert position != null;
this.origin = origin;
this.position = position;
this.message = message;
diff --git a/src/main/java/com/android/tools/r8/utils/TriConsumer.java b/src/main/java/com/android/tools/r8/utils/TriConsumer.java
new file mode 100644
index 0000000..e42aa62
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/TriConsumer.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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;
+
+public interface TriConsumer<S, T, U> {
+
+ void accept(S s, T t, U u);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
index 954eae1..1c6d63a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -7,17 +7,29 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.function.IntFunction;
-public class LongLivedProgramMethodSetBuilder {
+public class LongLivedProgramMethodSetBuilder<T extends ProgramMethodSet> {
- private Set<DexMethod> methods = Sets.newIdentityHashSet();
+ private final IntFunction<T> factory;
+ private final Set<DexMethod> methods = Sets.newIdentityHashSet();
- public LongLivedProgramMethodSetBuilder() {}
+ private LongLivedProgramMethodSetBuilder(IntFunction<T> factory) {
+ this.factory = factory;
+ }
+
+ public static LongLivedProgramMethodSetBuilder<?> create() {
+ return new LongLivedProgramMethodSetBuilder<>(ProgramMethodSet::create);
+ }
+
+ public static LongLivedProgramMethodSetBuilder<SortedProgramMethodSet> createSorted() {
+ return new LongLivedProgramMethodSetBuilder<>(ignore -> SortedProgramMethodSet.create());
+ }
public void add(ProgramMethod method) {
methods.add(method.getReference());
@@ -27,15 +39,23 @@
methods.forEach(this::add);
}
- public ProgramMethodSet build(AppView<AppInfoWithLiveness> appView) {
- return build(appView, ProgramMethodSet::create);
+ public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
+ Set<DexMethod> newMethods = Sets.newIdentityHashSet();
+ for (DexMethod method : methods) {
+ newMethods.add(appView.graphLense().getRenamedMethodSignature(method, applied));
+ }
+ methods.clear();
+ methods.addAll(newMethods);
}
- public <T extends ProgramMethodSet> T build(
- AppView<AppInfoWithLiveness> appView, IntFunction<T> factory) {
+ public T build(AppView<AppInfoWithLiveness> appView) {
+ return build(appView, null);
+ }
+
+ public T build(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
T result = factory.apply(methods.size());
for (DexMethod oldMethod : methods) {
- DexMethod method = appView.graphLense().getRenamedMethodSignature(oldMethod);
+ DexMethod method = appView.graphLense().getRenamedMethodSignature(oldMethod, applied);
DexProgramClass holder = appView.definitionForHolder(method).asProgramClass();
result.createAndAdd(holder, holder.lookupMethod(method));
}
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
index 04c4bf2..441be7c 100644
--- a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.InternalOptions;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -41,7 +41,7 @@
@Test
public void test() throws Exception {
- Reporter reporter = new Reporter();
+ InternalOptions options = new InternalOptions();
String dataResourceName = "my-resource.bin";
byte[] dataResourceData = new byte[] {1, 2, 3};
@@ -50,7 +50,7 @@
testForD8().addProgramClasses(B.class).setMinApi(AndroidApiLevel.B).compile().writeToZip();
AndroidApp appIn =
- AndroidApp.builder(reporter)
+ AndroidApp.builder(options.reporter)
.addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
.addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
.addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
@@ -67,9 +67,9 @@
.build();
Path dumpFile = temp.newFolder().toPath().resolve("dump.zip");
- appIn.dump(dumpFile, null, reporter);
+ appIn.dump(dumpFile, options);
- AndroidApp appOut = AndroidApp.builder(reporter).addDump(dumpFile).build();
+ AndroidApp appOut = AndroidApp.builder(options.reporter).addDump(dumpFile).build();
assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
assertEquals(
DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()),
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index ce6cbcc..cbc5c01 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -119,7 +119,9 @@
// Art.
containsString(
"java.lang.NoClassDefFoundError: "
- + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;")));
+ + "Failed resolution of: Ljava/util/concurrent/Flow$Subscriber;"),
+ // Art 10+.
+ containsString("java.lang.ClassNotFoundException: MySubscriber")));
} else {
if (parameters.getRuntime().asCf().getVm() == CfVm.JDK8) {
// java.util.concurrent.Flow$Subscriber not present in JDK8.
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 8d43d07..ce051a5 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -131,12 +131,13 @@
Path working = temp.getRoot().toPath();
Path flags = working.resolve("flags.txt").toAbsolutePath();
assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
- DiagnosticsChecker.checkErrorsContains("File not found", handler ->
- D8.run(
- D8Command.parse(
- new String[] { "@" + flags.toString() },
- EmbeddedOrigin.INSTANCE,
- handler).build()));
+ DiagnosticsChecker.checkErrorsContains(
+ "NoSuchFileException",
+ handler ->
+ D8.run(
+ D8Command.parse(
+ new String[] {"@" + flags.toString()}, EmbeddedOrigin.INSTANCE, handler)
+ .build()));
}
@Test(expected = CompilationFailedException.class)
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
new file mode 100644
index 0000000..5f6b792
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public abstract class DiagnosticsMatcher extends TypeSafeMatcher<Diagnostic> {
+
+ public static Matcher<Diagnostic> diagnosticMessage(Matcher<String> messageMatcher) {
+ return new DiagnosticsMatcher() {
+ @Override
+ protected boolean eval(Diagnostic diagnostic) {
+ return messageMatcher.matches(diagnostic.getDiagnosticMessage());
+ }
+
+ @Override
+ protected void explain(Description description) {
+ description.appendText("message with ");
+ messageMatcher.describeTo(description);
+ }
+ };
+ }
+
+ public static Matcher<Diagnostic> diagnosticType(Class<? extends Diagnostic> type) {
+ return new DiagnosticsMatcher() {
+ @Override
+ protected boolean eval(Diagnostic diagnostic) {
+ return type.isInstance(diagnostic);
+ }
+
+ @Override
+ protected void explain(Description description) {
+ description.appendText("type ").appendText(type.getSimpleName());
+ }
+ };
+ }
+
+ public static Matcher<Diagnostic> diagnosticException(Class<? extends Throwable> exception) {
+ return new DiagnosticsMatcher() {
+ @Override
+ protected boolean eval(Diagnostic diagnostic) {
+ return diagnostic instanceof ExceptionDiagnostic
+ && exception.isInstance(((ExceptionDiagnostic) diagnostic).getCause());
+ }
+
+ @Override
+ protected void explain(Description description) {
+ description.appendText("exception type ").appendText(exception.getSimpleName());
+ }
+ };
+ }
+
+ public static Matcher<Diagnostic> diagnosticOrigin(Origin origin) {
+ return new DiagnosticsMatcher() {
+ @Override
+ protected boolean eval(Diagnostic diagnostic) {
+ return diagnostic.getOrigin().equals(origin);
+ }
+
+ @Override
+ protected void explain(Description description) {
+ description.appendText("orgin ").appendText(origin.toString());
+ }
+ };
+ }
+
+ @Override
+ protected boolean matchesSafely(Diagnostic diagnostic) {
+ return eval(diagnostic);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ explain(description.appendText("a diagnostic "));
+ }
+
+ protected abstract boolean eval(Diagnostic diagnostic);
+
+ protected abstract void explain(Description description);
+}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 29a9e22..4f8806c 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -150,12 +150,13 @@
Path working = temp.getRoot().toPath();
Path flags = working.resolve("flags.txt").toAbsolutePath();
assertNotEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
- DiagnosticsChecker.checkErrorsContains("File not found", handler ->
- R8.run(
- R8Command.parse(
- new String[] { "@" + flags.toString() },
- EmbeddedOrigin.INSTANCE,
- handler).build()));
+ DiagnosticsChecker.checkErrorsContains(
+ "NoSuchFileException",
+ handler ->
+ R8.run(
+ R8Command.parse(
+ new String[] {"@" + flags.toString()}, EmbeddedOrigin.INSTANCE, handler)
+ .build()));
}
@Test(expected = CompilationFailedException.class)
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 8ffe6af..6326718 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -169,7 +169,8 @@
private static final Multimap<String, TestCondition> timeoutOrSkipRunWithArt =
new ImmutableListMultimap.Builder<String, TestCondition>()
// Loops on art - timeout.
- .put("109-suspend-check",
+ .put(
+ "109-suspend-check",
TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
// Flaky loops on art.
.put("129-ThreadGetId", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
@@ -177,8 +178,13 @@
// tests on 5.1.1 makes our buildbot cycles time too long.
.put("800-smali", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
// Hangs on dalvik.
- .put("802-deoptimization",
+ .put(
+ "802-deoptimization",
TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+ // TODO(b/144975341): Triage
+ .put(
+ "134-reg-promotion",
+ TestCondition.match(TestCondition.runtimes(DexVm.Version.V10_0_0)))
.build();
// Tests that are flaky with the Art version we currently use.
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 9252555..8b19ae3 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -71,6 +71,10 @@
Version.V9_0_0,
// TODO(120402963) Triage.
ImmutableList.of("invokecustom-with-shrinking", "invokecustom2-with-shrinking"))
+ .put(
+ Version.V10_0_0,
+ // TODO(120402963) Triage.
+ ImmutableList.of("invokecustom-with-shrinking", "invokecustom2-with-shrinking"))
.put(Version.DEFAULT, ImmutableList.of())
.build();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 174c55d..5a90d52 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -190,8 +190,8 @@
}
Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
-
DexVm vm = ToolHelper.getDexVm();
+ Assume.assumeFalse("Triage (b/144966342)", vm.isNewerThan(DexVm.ART_9_0_0_HOST));
if (shouldSkipVm(vm.getVersion())) {
return;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index e28b4eb..a109e20 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -138,7 +138,7 @@
graphConsumer);
switch (allowedDiagnosticMessages) {
case ALL:
- compileResult.assertDiagnosticMessageThatMatches(new IsAnything<>());
+ compileResult.assertDiagnosticThatMatches(new IsAnything<>());
break;
case ERROR:
compileResult.assertOnlyErrors();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index daab81d..6568713 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -202,7 +202,8 @@
ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder();
builder
.put(
- DexVm.Version.V4_0_4, ImmutableList.of(
+ DexVm.Version.V4_0_4,
+ ImmutableList.of(
// API not supported
"paramnames",
"repeat_annotations_new_api",
@@ -214,10 +215,10 @@
"StaticMethodInAndroidJar25",
"testMissingInterfaceDesugared2AndroidO",
"testCallToMissingSuperInterfaceDesugaredAndroidO",
- "testMissingSuperDesugaredAndroidO"
- ))
+ "testMissingSuperDesugaredAndroidO"))
.put(
- DexVm.Version.V4_4_4, ImmutableList.of(
+ DexVm.Version.V4_4_4,
+ ImmutableList.of(
// API not supported
"paramnames",
"repeat_annotations_new_api",
@@ -229,10 +230,10 @@
"StaticMethodInAndroidJar25",
"testMissingInterfaceDesugared2AndroidO",
"testCallToMissingSuperInterfaceDesugaredAndroidO",
- "testMissingSuperDesugaredAndroidO"
- ))
+ "testMissingSuperDesugaredAndroidO"))
.put(
- DexVm.Version.V5_1_1, ImmutableList.of(
+ DexVm.Version.V5_1_1,
+ ImmutableList.of(
// API not supported
"paramnames",
"repeat_annotations_new_api",
@@ -244,10 +245,10 @@
"StaticMethodInAndroidJar25",
"testMissingInterfaceDesugared2AndroidO",
"testCallToMissingSuperInterfaceDesugaredAndroidO",
- "testMissingSuperDesugaredAndroidO"
- ))
+ "testMissingSuperDesugaredAndroidO"))
.put(
- DexVm.Version.V6_0_1, ImmutableList.of(
+ DexVm.Version.V6_0_1,
+ ImmutableList.of(
// API not supported
"paramnames",
"repeat_annotations_new_api",
@@ -259,10 +260,10 @@
"StaticMethodInAndroidJar25",
"testMissingInterfaceDesugared2AndroidO",
"testCallToMissingSuperInterfaceDesugaredAndroidO",
- "testMissingSuperDesugaredAndroidO"
- ))
+ "testMissingSuperDesugaredAndroidO"))
.put(
- DexVm.Version.V7_0_0, ImmutableList.of(
+ DexVm.Version.V7_0_0,
+ ImmutableList.of(
// API not supported
"paramnames",
// Dex version not supported
@@ -271,17 +272,18 @@
"invokecustom2",
"testMissingInterfaceDesugared2AndroidO",
"testCallToMissingSuperInterfaceDesugaredAndroidO",
- "testMissingSuperDesugaredAndroidO"
- ))
+ "testMissingSuperDesugaredAndroidO"))
.put(
- DexVm.Version.V9_0_0, ImmutableList.of(
+ DexVm.Version.V9_0_0,
+ ImmutableList.of(
// TODO(120402963): Triage.
- "invokecustom",
- "invokecustom2"
- ))
+ "invokecustom", "invokecustom2"))
.put(
- DexVm.Version.DEFAULT, ImmutableList.of()
- );
+ DexVm.Version.V10_0_0,
+ ImmutableList.of(
+ // TODO(120402963): Triage.
+ "invokecustom", "invokecustom2"))
+ .put(DexVm.Version.DEFAULT, ImmutableList.of());
failsOn = builder.build();
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 000ffbc..aeaf585 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -96,7 +96,6 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiFunction;
@@ -580,7 +579,7 @@
DexApplication dexApplication =
new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
return new AppInfo(dexApplication);
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
@@ -591,7 +590,7 @@
DexApplication dexApplication =
new ApplicationReader(application, new InternalOptions(), Timing.empty()).read();
return new AppInfoWithClassHierarchy(dexApplication);
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
@@ -1384,6 +1383,7 @@
}
}
+ @Deprecated
public static Path runtimeJar(Backend backend) {
if (backend == Backend.DEX) {
return ToolHelper.getDefaultAndroidJar();
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index e9ac10d..21ab2b1 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -12,7 +12,6 @@
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
@@ -39,10 +38,21 @@
return self();
}
- public T ifTrue(boolean value, Consumer<T> consumer) {
+ public T applyIf(boolean value, ThrowableConsumer<T> consumer) {
T self = self();
if (value) {
- consumer.accept(self);
+ consumer.acceptWithRuntimeException(self);
+ }
+ return self;
+ }
+
+ public T applyIf(
+ boolean value, ThrowableConsumer<T> trueConsumer, ThrowableConsumer<T> falseConsumer) {
+ T self = self();
+ if (value) {
+ trueConsumer.acceptWithRuntimeException(self);
+ } else {
+ falseConsumer.acceptWithRuntimeException(self);
}
return self;
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 7f777ed..39356e1 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.TestBase.Backend.DEX;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
@@ -288,13 +289,13 @@
return self();
}
- public CR assertDiagnosticMessageThatMatches(Matcher<String> matcher) {
- getDiagnosticMessages().assertDiagnosticMessageThatMatches(matcher);
+ public CR assertDiagnosticThatMatches(Matcher<Diagnostic> matcher) {
+ getDiagnosticMessages().assertDiagnosticThatMatches(matcher);
return self();
}
public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
- getDiagnosticMessages().assertInfoMessageThatMatches(matcher);
+ getDiagnosticMessages().assertInfoThatMatches(diagnosticMessage(matcher));
return self();
}
@@ -303,7 +304,7 @@
}
public CR assertNoInfoMessageThatMatches(Matcher<String> matcher) {
- getDiagnosticMessages().assertNoInfoMessageThatMatches(matcher);
+ getDiagnosticMessages().assertNoInfosMatch(diagnosticMessage(matcher));
return self();
}
@@ -328,7 +329,7 @@
}
public CR assertAllWarningMessagesMatch(Matcher<String> matcher) {
- getDiagnosticMessages().assertNoWarningMessageThatMatches(not(matcher));
+ getDiagnosticMessages().assertAllWarningsMatch(diagnosticMessage(matcher));
return self();
}
@@ -338,7 +339,7 @@
}
public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) {
- getDiagnosticMessages().assertNoWarningMessageThatMatches(matcher);
+ getDiagnosticMessages().assertNoWarningsMatch(diagnosticMessage(matcher));
return self();
}
@@ -352,11 +353,6 @@
return self();
}
- public CR assertNoErrorMessageThatMatches(Matcher<String> matcher) {
- getDiagnosticMessages().assertNoErrorMessageThatMatches(matcher);
- return self();
- }
-
public CR assertNoStdout() {
assertEquals("", getStdout());
return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 78dee67..be6ffbb 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -146,8 +146,13 @@
}
}
- public CR compileWithExpectedDiagnostics(
- Consumer<TestDiagnosticMessages> diagnosticsConsumer) throws CompilationFailedException {
+ @FunctionalInterface
+ public interface DiagnosticsConsumer {
+ void accept(TestDiagnosticMessages diagnostics);
+ }
+
+ public CR compileWithExpectedDiagnostics(DiagnosticsConsumer diagnosticsConsumer)
+ throws CompilationFailedException {
TestDiagnosticMessages diagnosticsHandler = getState().getDiagnosticsMessages();
try {
CR result = compile();
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 0dd7e67..0cab77c 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -4,6 +4,9 @@
package com.android.tools.r8;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.not;
+
import java.util.List;
import org.hamcrest.Matcher;
@@ -29,21 +32,61 @@
TestDiagnosticMessages assertErrorsCount(int count);
- TestDiagnosticMessages assertDiagnosticMessageThatMatches(Matcher<String> matcher);
+ // Match exact.
- TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher);
+ TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic>... matchers);
- TestDiagnosticMessages assertAllInfoMessagesMatch(Matcher<String> matcher);
+ TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic>... matchers);
- TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher);
+ TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers);
- TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
+ TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers);
- TestDiagnosticMessages assertAllWarningMessagesMatch(Matcher<String> matcher);
+ // Match one.
- TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+ TestDiagnosticMessages assertDiagnosticThatMatches(Matcher<Diagnostic> matcher);
- TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+ TestDiagnosticMessages assertInfoThatMatches(Matcher<Diagnostic> matcher);
- TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
+ TestDiagnosticMessages assertWarningThatMatches(Matcher<Diagnostic> matcher);
+
+ TestDiagnosticMessages assertErrorThatMatches(Matcher<Diagnostic> matcher);
+
+ // Consider removing this helper.
+ default TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
+ return assertWarningThatMatches(diagnosticMessage(matcher));
+ }
+
+ // Consider removing this helper.
+ default TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
+ return assertErrorThatMatches(diagnosticMessage(matcher));
+ }
+
+ // Match all.
+
+ TestDiagnosticMessages assertAllDiagnosticsMatch(Matcher<Diagnostic> matcher);
+
+ TestDiagnosticMessages assertAllInfosMatch(Matcher<Diagnostic> matcher);
+
+ TestDiagnosticMessages assertAllWarningsMatch(Matcher<Diagnostic> matcher);
+
+ TestDiagnosticMessages assertAllErrorsMatch(Matcher<Diagnostic> matcher);
+
+ // Match none.
+
+ default TestDiagnosticMessages assertNoDiagnosticsMatch(Matcher<Diagnostic> matcher) {
+ return assertAllDiagnosticsMatch(not(matcher));
+ }
+
+ default TestDiagnosticMessages assertNoInfosMatch(Matcher<Diagnostic> matcher) {
+ return assertAllInfosMatch(not(matcher));
+ }
+
+ default TestDiagnosticMessages assertNoWarningsMatch(Matcher<Diagnostic> matcher) {
+ return assertAllWarningsMatch(not(matcher));
+ }
+
+ default TestDiagnosticMessages assertNoErrorsMatch(Matcher<Diagnostic> matcher) {
+ return assertAllErrorsMatch(not(matcher));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index eba971d..2635732 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -4,7 +4,7 @@
package com.android.tools.r8;
-import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
@@ -12,7 +12,10 @@
import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.hamcrest.Matcher;
public class TestDiagnosticMessagesImpl implements DiagnosticsHandler, TestDiagnosticMessages {
@@ -137,19 +140,26 @@
return this;
}
- private TestDiagnosticMessages assertMessageThatMatches(
- Iterable<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
+ private TestDiagnosticMessages assertAllDiagnosticsMatches(
+ Iterable<Diagnostic> diagnostics, String tag, Matcher<Diagnostic> matcher) {
+ for (Diagnostic diagnostic : diagnostics) {
+ assertThat(diagnostic, matcher);
+ }
+ return this;
+ }
+
+ private TestDiagnosticMessages assertDiagnosticThatMatches(
+ Iterable<Diagnostic> diagnostics, String tag, Matcher<Diagnostic> matcher) {
int numberOfDiagnostics = 0;
for (Diagnostic diagnostic : diagnostics) {
- if (matcher.matches(diagnostic.getDiagnosticMessage())) {
+ if (matcher.matches(diagnostic)) {
return this;
}
numberOfDiagnostics++;
}
- assertNotEquals(0, numberOfDiagnostics);
StringBuilder builder = new StringBuilder("No " + tag + " matches " + matcher.toString());
builder.append(System.lineSeparator());
- if (getWarnings().size() == 0) {
+ if (numberOfDiagnostics == 0) {
builder.append("There were no " + tag + "s.");
} else {
builder.append("There were " + numberOfDiagnostics + " " + tag + "s:");
@@ -163,60 +173,132 @@
return this;
}
- private TestDiagnosticMessages assertNoMessageThatMatches(
- List<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
- for (int i = 0; i < diagnostics.size(); i++) {
- String message = diagnostics.get(i).getDiagnosticMessage();
- if (matcher.matches(message)) {
- fail("The " + tag + ": \"" + message + "\" + matches " + matcher + ".");
+ private static void assertDiagnosticsMatch(
+ Iterable<Diagnostic> diagnostics, String tag, List<Matcher<Diagnostic>> matchers) {
+ // Match is unordered, but we make no attempts to find the maximum match.
+ int diagnosticsCount = 0;
+ Set<Diagnostic> matchedDiagnostics = new HashSet<>();
+ Set<Matcher<Diagnostic>> matchedMatchers = new HashSet<>();
+ for (Diagnostic diagnostic : diagnostics) {
+ diagnosticsCount++;
+ for (Matcher<Diagnostic> matcher : matchers) {
+ if (matchedMatchers.contains(matcher)) {
+ continue;
+ }
+ if (matcher.matches(diagnostic)) {
+ matchedDiagnostics.add(diagnostic);
+ matchedMatchers.add(matcher);
+ break;
+ }
}
}
+ StringBuilder builder = new StringBuilder();
+ boolean failedMatching = false;
+ if (matchedDiagnostics.size() < diagnosticsCount) {
+ failedMatching = true;
+ builder.append("\nUnmatched diagnostics:");
+ for (Diagnostic diagnostic : diagnostics) {
+ if (!matchedDiagnostics.contains(diagnostic)) {
+ builder
+ .append("\n - ")
+ .append(diagnostics.getClass().getName())
+ .append(diagnostic.getDiagnosticMessage());
+ }
+ }
+ }
+ if (matchedMatchers.size() < matchers.size()) {
+ failedMatching = true;
+ builder.append("\nUnmatched matchers:");
+ for (Matcher<Diagnostic> matcher : matchers) {
+ if (!matchedMatchers.contains(matcher)) {
+ builder.append("\n - ").append(matcher);
+ }
+ }
+ }
+ if (failedMatching) {
+ builder.append("\nAll diagnostics:");
+ for (Diagnostic diagnostic : diagnostics) {
+ builder
+ .append("\n - ")
+ .append(diagnostics.getClass().getName())
+ .append(diagnostic.getDiagnosticMessage());
+ }
+ builder.append("\nAll matchers:");
+ for (Matcher<Diagnostic> matcher : matchers) {
+ builder.append("\n - ").append(matcher);
+ }
+ fail(builder.toString());
+ }
+ // Double check consistency.
+ assertEquals(matchers.size(), diagnosticsCount);
+ assertEquals(diagnosticsCount, matchedDiagnostics.size());
+ assertEquals(diagnosticsCount, matchedMatchers.size());
+ }
+
+ @Override
+ public TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic>... matchers) {
+ assertDiagnosticsMatch(getAllDiagnostics(), "diagnostics", Arrays.asList(matchers));
return this;
}
@Override
- public TestDiagnosticMessages assertDiagnosticMessageThatMatches(Matcher<String> matcher) {
- return assertMessageThatMatches(
- Iterables.concat(getInfos(), getWarnings(), getErrors()), "diagnostic message", matcher);
+ public TestDiagnosticMessages assertInfosMatch(Matcher<Diagnostic>... matchers) {
+ assertDiagnosticsMatch(getInfos(), "infos", Arrays.asList(matchers));
+ return this;
}
@Override
- public TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher) {
- return assertMessageThatMatches(getInfos(), "info", matcher);
+ public TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers) {
+ assertDiagnosticsMatch(getWarnings(), "warnings", Arrays.asList(matchers));
+ return this;
}
@Override
- public TestDiagnosticMessages assertAllInfoMessagesMatch(Matcher<String> matcher) {
- return assertNoInfoMessageThatMatches(not(matcher));
+ public TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic>... matchers) {
+ assertDiagnosticsMatch(getErrors(), "errors", Arrays.asList(matchers));
+ return this;
}
@Override
- public TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher) {
- return assertNoMessageThatMatches(getInfos(), "info", matcher);
+ public TestDiagnosticMessages assertDiagnosticThatMatches(Matcher<Diagnostic> matcher) {
+ return assertDiagnosticThatMatches(getAllDiagnostics(), "diagnostic message", matcher);
+ }
+
+ private Iterable<Diagnostic> getAllDiagnostics() {
+ return Iterables.concat(getInfos(), getWarnings(), getErrors());
}
@Override
- public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
- return assertMessageThatMatches(getWarnings(), "warning", matcher);
+ public TestDiagnosticMessages assertInfoThatMatches(Matcher<Diagnostic> matcher) {
+ return assertDiagnosticThatMatches(getInfos(), "info", matcher);
+ }
+
+ public TestDiagnosticMessages assertWarningThatMatches(Matcher<Diagnostic> matcher) {
+ return assertDiagnosticThatMatches(getWarnings(), "warning", matcher);
}
@Override
- public TestDiagnosticMessages assertAllWarningMessagesMatch(Matcher<String> matcher) {
- return assertNoWarningMessageThatMatches(not(matcher));
+ public TestDiagnosticMessages assertErrorThatMatches(Matcher<Diagnostic> matcher) {
+ return assertDiagnosticThatMatches(getErrors(), "error", matcher);
}
@Override
- public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
- return assertNoMessageThatMatches(getWarnings(), "warning", matcher);
+ public TestDiagnosticMessages assertAllDiagnosticsMatch(Matcher<Diagnostic> matcher) {
+ return assertAllDiagnosticsMatches(getAllDiagnostics(), "diagnostic message", matcher);
}
@Override
- public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher) {
- return assertMessageThatMatches(getErrors(), "error", matcher);
+ public TestDiagnosticMessages assertAllInfosMatch(Matcher<Diagnostic> matcher) {
+ return assertAllDiagnosticsMatches(getInfos(), "info", matcher);
}
@Override
- public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher) {
- return assertNoMessageThatMatches(getErrors(), "error", matcher);
+ public TestDiagnosticMessages assertAllWarningsMatch(Matcher<Diagnostic> matcher) {
+ return assertAllDiagnosticsMatches(getWarnings(), "warning", matcher);
+ }
+
+ @Override
+ public TestDiagnosticMessages assertAllErrorsMatch(Matcher<Diagnostic> matcher) {
+ return assertAllDiagnosticsMatches(getErrors(), "error", matcher);
}
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index fab7d11..b262e4e 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -70,6 +70,10 @@
return addKeepRules(Arrays.asList(rules));
}
+ public T addKeepKotlinMetadata() {
+ return addKeepRules("-keep class kotlin.Metadata { *; }");
+ }
+
public T addKeepAllClassesRule() {
return addKeepRules("-keep class ** { *; }");
}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
index 940479a..61c1d77 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PrivateKeptMembersPublicizerTest.java
@@ -5,14 +5,16 @@
package com.android.tools.r8.accessrelaxation;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -22,21 +24,41 @@
public class PrivateKeptMembersPublicizerTest extends TestBase {
private final TestParameters parameters;
+ private final boolean withKeepAllowAccessModification;
+ private final boolean withPrecondition;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameters(name = "{0}, with keep allow access modification: {1}, with precondition: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
}
- public PrivateKeptMembersPublicizerTest(TestParameters parameters) {
+ public PrivateKeptMembersPublicizerTest(
+ TestParameters parameters,
+ boolean withKeepAllowAccessModification,
+ boolean withPrecondition) {
this.parameters = parameters;
+ this.withKeepAllowAccessModification = withKeepAllowAccessModification;
+ this.withPrecondition = withPrecondition;
}
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(PrivateKeptMembersPublicizerTest.class)
- .addKeepClassAndMembersRules(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ withPrecondition ? "-if class *" : "",
+ "-keep"
+ + (withKeepAllowAccessModification ? ",allowaccessmodification" : "")
+ + " class "
+ + typeName(TestClass.class)
+ + " {",
+ " private static java.lang.String greeting;",
+ " private static void greet(java.lang.String);",
+ "}")
.allowAccessModification()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -48,8 +70,13 @@
private void inspect(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(TestClass.class);
assertThat(classSubject, isPresent());
- assertTrue(classSubject.uniqueFieldWithName("greeting").isPrivate());
- assertTrue(classSubject.uniqueMethodWithName("greet").isPrivate());
+ if (withKeepAllowAccessModification) {
+ assertThat(classSubject.uniqueFieldWithName("greeting"), isPublic());
+ assertThat(classSubject.uniqueMethodWithName("greet"), isPublic());
+ } else {
+ assertThat(classSubject.uniqueFieldWithName("greeting"), isPrivate());
+ assertThat(classSubject.uniqueMethodWithName("greet"), isPrivate());
+ }
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
index 56cde8f..524cbfa 100644
--- a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.CompilationFailedException;
@@ -17,10 +16,10 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.retrace.KotlinInlineFunctionRetraceTest;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -59,19 +58,13 @@
CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
inspectSourceDebugExtension(kotlinInspector);
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(kotlinSources)
.addKeepAttributes(ProguardKeepAttributes.SOURCE_DEBUG_EXTENSION)
.addKeepAllClassesRule()
.setMode(CompilationMode.RELEASE)
.setMinApi(parameters.getApiLevel())
- .allowDiagnosticWarningMessages(
- parameters.isDexRuntime()
- && parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M))
.compile()
- .assertAllWarningMessagesMatch(
- containsString(
- "Type `kotlin.jvm.internal.Intrinsics` was not found, it is required for default"
- + " or static interface methods"))
.inspect(this::inspectSourceDebugExtension);
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 6a0cf4d..76ebd58 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -453,7 +453,7 @@
// Callees are invoked with a simple constant, e.g., "Bar". Propagating it into the callees
// bothers what the tests want to check, such as exact instructions in the body that include
// invocation kinds, like virtual call to a bridge.
- options.enablePropagationOfConstantsAtCallSites = false;
+ assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
// Disable inlining to avoid the (short) tested method from being inlined and then removed.
options.enableInlining = false;
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgesAndNonBridgesHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgesAndNonBridgesHoistingTest.java
new file mode 100644
index 0000000..c6a1c88
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/BridgesAndNonBridgesHoistingTest.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2020, 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.bridgeremoval.hoisting;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BridgesAndNonBridgesHoistingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public BridgesAndNonBridgesHoistingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {}
+
+ static class TestClass {
+
+ public static void main(String[] args) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
index d6eb6cd..d93514a 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/NonReboundBridgeHoistingTest.java
@@ -56,11 +56,11 @@
ClassSubject aClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.getClassA());
assertThat(aClassSubject, isPresent());
assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
- assertThat(aClassSubject.uniqueMethodWithName("bridge"), not(isPresent()));
+ assertThat(aClassSubject.uniqueMethodWithName("bridge"), isPresent());
ClassSubject bClassSubject = inspector.clazz(NonReboundBridgeHoistingTestClasses.B.class);
assertThat(bClassSubject, isPresent());
- assertThat(bClassSubject.uniqueMethodWithName("bridge"), isPresent());
+ assertThat(bClassSubject.uniqueMethodWithName("bridge"), not(isPresent()));
ClassSubject cClassSubject = inspector.clazz(C.class);
assertThat(cClassSubject, isPresent());
@@ -79,8 +79,8 @@
// The invoke instruction in this bridge cannot be rewritten to target A.m(), since A is not
// accessible in this context. It therefore points to B.m(), where there is no definition of the
- // method. As a result of this, we cannot move this bridge to A without also rewriting the
- // signature referenced from the invoke instruction.
+ // method. When the bridge is hoisted to B.m(), the invoke-virtual instruction can be rewritten
+ // to target A.m(). This allows hoisting the bridge further from B.m() to A.m().
@NeverInline
public /*bridge*/ void bridge() {
m();
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
index 89437f1..d764895 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/hoisting/PositiveBridgeHoistingTest.java
@@ -35,7 +35,7 @@
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class, A.class, B3.class)
+ .addProgramClasses(TestClass.class, A.class, B3.class, B4.class)
.addProgramClassFileData(
transformer(B1.class)
.setBridge(B1.class.getDeclaredMethod("superBridge", Object.class))
@@ -44,6 +44,10 @@
transformer(B2.class)
.setBridge(B2.class.getDeclaredMethod("superBridge", Object.class))
.setBridge(B2.class.getDeclaredMethod("virtualBridge", Object.class))
+ .transform(),
+ transformer(B5.class)
+ .setBridge(B5.class.getDeclaredMethod("superBridge", Object.class))
+ .setBridge(B5.class.getDeclaredMethod("virtualBridge", Object.class))
.transform())
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
@@ -71,16 +75,30 @@
assertThat(b2ClassSubject, isPresent());
assertThat(b2ClassSubject.uniqueMethodWithName("superBridge"), not(isPresent()));
assertThat(b2ClassSubject.uniqueMethodWithName("virtualBridge"), not(isPresent()));
+
+ ClassSubject b4ClassSubject = inspector.clazz(B4.class);
+ assertThat(b4ClassSubject, isPresent());
+ assertThat(b4ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+ assertThat(b4ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
+
+ ClassSubject b5ClassSubject = inspector.clazz(B5.class);
+ assertThat(b5ClassSubject, isPresent());
+ assertThat(b5ClassSubject.uniqueMethodWithName("superBridge"), isPresent());
+ assertThat(b5ClassSubject.uniqueMethodWithName("virtualBridge"), isPresent());
}
static class TestClass {
public static void main(String[] args) {
- System.out.print(new B1().superBridge("Hello"));
- System.out.print(new B1().virtualBridge(" "));
- System.out.print(new B2().superBridge("world"));
- System.out.print(new B2().virtualBridge("!"));
- System.out.println(new B3().m(""));
+ System.out.print(new B1().superBridge("Hel"));
+ System.out.print(new B1().virtualBridge("lo"));
+ System.out.print(new B2().superBridge(" "));
+ System.out.print(new B2().virtualBridge("w"));
+ System.out.print(new B3().m("o"));
+ System.out.print(new B4().superBridge("r"));
+ System.out.print(new B4().virtualBridge("l"));
+ System.out.print(new B5().superBridge("d"));
+ System.out.println(new B5().virtualBridge("!"));
}
}
@@ -90,6 +108,11 @@
public Object m(String arg) {
return System.currentTimeMillis() >= 0 ? arg : null;
}
+
+ @NeverInline
+ public Object m2(String arg) {
+ return System.currentTimeMillis() >= 0 ? arg : null;
+ }
}
@NeverClassInline
@@ -137,4 +160,36 @@
// but this should never be the case in practice.
@NeverClassInline
static class B3 extends A {}
+
+ // The fact that this class declares superBridge() and virtualBridge() should not prevent
+ // us from hoisting other bridges to A.
+ @NeverClassInline
+ static class B4 extends A {
+
+ @NeverInline
+ public String superBridge(Object o) {
+ return System.currentTimeMillis() >= 0 ? ((String) o) : null;
+ }
+
+ @NeverInline
+ public String virtualBridge(Object o) {
+ return System.currentTimeMillis() >= 0 ? ((String) o) : null;
+ }
+ }
+
+ // This class declares the same bridges, but with different (bridge) behavior. They are candidates
+ // for hoisting, but will not be hoisted because it is better to hoist the bridges declared on B1.
+ @NeverClassInline
+ static class B5 extends A {
+
+ @NeverInline
+ public /*bridge*/ String superBridge(Object o) {
+ return (String) super.m2((String) o);
+ }
+
+ @NeverInline
+ public /*bridge*/ String virtualBridge(Object o) {
+ return (String) m2((String) o);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/code/PassThroughTest.java b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
index b1a9f2f..673f3a2 100644
--- a/src/test/java/com/android/tools/r8/code/PassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/code/PassThroughTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.CfFrontendExamplesTest;
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.DirectoryClassFileProvider;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestShrinkerBuilder;
@@ -21,6 +22,7 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,7 +32,7 @@
@RunWith(Parameterized.class)
public class PassThroughTest extends TestBase {
- private final String EXPECTED = StringUtils.lines("0", "foo", "0");
+ private final String EXPECTED = StringUtils.lines("0", "foo", "0", "foo", "foo");
private final TestParameters parameters;
private final boolean keepDebug;
@@ -56,7 +58,8 @@
// Check that reading the same input is actual matches.
ClassFileResourceProvider original =
DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
- verifyInstructionsForMainMatchingExpectation(original, true, true);
+ verifyInstructionsForMethodMatchingExpectation(original, "main", true, true);
+ verifyInstructionsForMethodMatchingExpectation(original, "exceptionTest", true, true);
}
@Test
@@ -64,14 +67,16 @@
Path outputJar = temp.newFile("output.jar").toPath();
testForR8(parameters.getBackend())
.addProgramClasses(Main.class)
- .addKeepMainRule(Main.class)
- .ifTrue(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
+ .addKeepAllClassesRule()
+ .enableInliningAnnotations()
+ .applyIf(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
.compile()
.writeToZip(outputJar)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(EXPECTED);
- verifyInstructionsForMainMatchingExpectation(
- new ArchiveClassFileProvider(outputJar), keepDebug, false);
+ ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
+ verifyInstructionsForMethodMatchingExpectation(actual, "main", keepDebug, false);
+ verifyInstructionsForMethodMatchingExpectation(actual, "exceptionTest", keepDebug, false);
}
@Test
@@ -79,22 +84,27 @@
Path outputJar = temp.newFile("output.jar").toPath();
testForR8(parameters.getBackend())
.addProgramClasses(Main.class)
- .addKeepMainRule(Main.class)
- .ifTrue(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
+ .addKeepAllClassesRule()
+ .enableInliningAnnotations()
+ .applyIf(keepDebug, TestShrinkerBuilder::addKeepAllAttributes)
.addOptionsModification(
- internalOptions ->
- internalOptions.testing.cfByteCodePassThrough =
- method -> method.method.name.toString().equals("main"))
+ internalOptions -> {
+ internalOptions.testing.cfByteCodePassThrough =
+ method -> !method.name.toString().equals("<init>");
+ internalOptions.testing.readInputStackMaps = true;
+ })
.compile()
.writeToZip(outputJar)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutput(EXPECTED);
- verifyInstructionsForMainMatchingExpectation(
- new ArchiveClassFileProvider(outputJar), keepDebug, true);
+ ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
+ verifyInstructionsForMethodMatchingExpectation(actual, "main", keepDebug, true);
+ verifyInstructionsForMethodMatchingExpectation(actual, "exceptionTest", keepDebug, true);
}
- private void verifyInstructionsForMainMatchingExpectation(
- ClassFileResourceProvider actual, boolean checkDebug, boolean expectation) throws Exception {
+ private void verifyInstructionsForMethodMatchingExpectation(
+ ClassFileResourceProvider actual, String methodName, boolean checkDebug, boolean expectation)
+ throws Exception {
ClassFileResourceProvider original =
DirectoryClassFileProvider.fromDirectory(ToolHelper.getClassPathForTests());
String descriptor = DescriptorUtils.javaTypeToDescriptor(Main.class.getTypeName());
@@ -103,43 +113,60 @@
if (!Arrays.equals(expectedBytes, actualBytes)) {
String expectedString = CfFrontendExamplesTest.asmToString(expectedBytes);
String actualString = CfFrontendExamplesTest.asmToString(actualBytes);
- verifyInstructionsForMainMatchingExpectation(
- getMethodInstructions(expectedString),
- getMethodInstructions(actualString),
+ verifyInstructionsForMethodMatchingExpectation(
+ getMethodInstructions(expectedString, methodName),
+ getMethodInstructions(actualString, methodName),
checkDebug,
expectation);
}
}
- private String getMethodInstructions(String asm) {
+ private String getMethodInstructions(String asm, String methodName) {
int methodIndexStart =
asm.indexOf(
- "methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, \"main\","
- + " \"([Ljava/lang/String;)V\", null, null);");
- int methodIndexEnd = asm.indexOf("}", methodIndexStart);
+ "methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, \""
+ + methodName
+ + "\",");
+ methodIndexStart = asm.indexOf("methodVisitor.visitCode();", methodIndexStart);
+ int methodIndexEnd = asm.indexOf("methodVisitor.visitEnd();", methodIndexStart);
return asm.substring(methodIndexStart, methodIndexEnd);
}
- private void verifyInstructionsForMainMatchingExpectation(
+ private void verifyInstructionsForMethodMatchingExpectation(
String originalInstructions,
String actualInstructions,
boolean checkDebug,
boolean expectation) {
- if (!checkDebug) {
- originalInstructions =
- StringUtils.splitLines(originalInstructions).stream()
- .filter(this::isNotDebugInstruction)
- .map(instr -> instr + "\n")
- .collect(Collectors.joining());
+ if (checkDebug) {
+ // We may rewrite jump instructions, so filter those out.
+ originalInstructions = filter(originalInstructions, this::isNotLabelOrJumpInstruction);
+ actualInstructions = filter(actualInstructions, this::isNotLabelOrJumpInstruction);
+ } else {
+ originalInstructions = filter(originalInstructions, this::isNotDebugInstructionOrJump);
+ actualInstructions = filter(actualInstructions, this::isNotLabelOrJumpInstruction);
}
assertSame(expectation, actualInstructions.equals(originalInstructions));
}
- private boolean isNotDebugInstruction(String instruction) {
+ private String filter(String instructions, Predicate<String> predicate) {
+ return StringUtils.splitLines(instructions).stream()
+ .filter(predicate)
+ .map(instr -> instr + "\n")
+ .collect(Collectors.joining());
+ }
+
+ private boolean isNotDebugInstructionOrJump(String instruction) {
return !(instruction.startsWith("methodVisitor.visitLocalVariable")
|| instruction.startsWith("methodVisitor.visitLabel")
|| instruction.startsWith("Label")
- || instruction.startsWith("methodVisitor.visitLineNumber"));
+ || instruction.startsWith("methodVisitor.visitLineNumber")
+ || instruction.startsWith("methodVisitor.visitJumpInsn"));
+ }
+
+ private boolean isNotLabelOrJumpInstruction(String instruction) {
+ return !(instruction.startsWith("Label")
+ || instruction.startsWith("methodVisitor.visitJumpInsn")
+ || instruction.startsWith("methodVisitor.visitLabel"));
}
public static class Main {
@@ -155,6 +182,28 @@
}
System.out.println(foo);
System.out.println(j);
+ System.out.println(phiTest(args.length > 0 ? args[0] : null));
+ System.out.println(exceptionTest(args.length > 0 ? args[0] : null));
+ }
+
+ @NeverInline
+ public static String phiTest(String arg) {
+ String result;
+ if (arg == null) {
+ result = "foo";
+ } else {
+ result = "bar";
+ }
+ return result;
+ }
+
+ @NeverInline
+ public static String exceptionTest(String arg) {
+ try {
+ return arg.toLowerCase();
+ } catch (NullPointerException ignored) {
+ return "foo";
+ }
}
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
new file mode 100644
index 0000000..63f1aa6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
@@ -0,0 +1,254 @@
+// Copyright (c) 2020, 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.desugar;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class DesugarToClassFileB156591935 extends TestBase implements Opcodes {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static AndroidApiLevel[] data() {
+ return AndroidApiLevel.values();
+ }
+
+ private final AndroidApiLevel apiLevel;
+
+ public DesugarToClassFileB156591935(AndroidApiLevel apiLevel) {
+ this.apiLevel = apiLevel;
+ }
+
+ private void expectNops(CodeInspector inspector, int numberOfNops) {
+ ClassSubject a = inspector.clazz("A");
+ assertEquals(
+ numberOfNops, a.clinit().streamInstructions().filter(InstructionSubject::isNop).count());
+ }
+
+ @Test
+ public void test() throws Exception {
+ // No nops in the input - see dump below.
+ // TODO(b/156591935): The three nops should be avoided.
+ testForD8(Backend.CF)
+ .addProgramClassFileData(dump())
+ .setMinApi(apiLevel)
+ .compile()
+ .inspect(inspector -> expectNops(inspector, 3));
+ }
+
+ /*
+ Dump of the compiled code for ths class below. The dump has not been modified, but the
+ code needs to have the specific line breaks for javac to insert the line numbers in the
+ way that this test is about. Used a dump to avoid source formatting invalidating the test.
+
+ static class A {
+ // The line break before createA is needed for the expected line info.
+ public static final A A_1 =
+ createA(1, "FIRST");
+ public static final A A_2 =
+ createA(1, "SECOND");
+ public static final A A_3 =
+ createA(1, "THIRD");
+
+ private final int value;
+ private final String name;
+
+ private static A createA(int value, String name) {
+ return new A(value, name);
+ }
+
+ private A(int value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ int getValue() {
+ return value;
+ }
+
+ String getName() {
+ return name;
+ }
+ }
+ */
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+ classWriter.visitSource("A.java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_1", "LA;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_2", "LA;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "A_3", "LA;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "value", "I", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(ACC_PRIVATE | ACC_FINAL, "name", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PRIVATE | ACC_STATIC, "createA", "(ILjava/lang/String;)LA;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(69, label0);
+ methodVisitor.visitTypeInsn(NEW, "A");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitVarInsn(ILOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "(ILjava/lang/String;)V", false);
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("value", "I", null, label0, label1, 0);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 1);
+ methodVisitor.visitMaxs(4, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "<init>", "(ILjava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(73, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitFieldInsn(PUTFIELD, "A", "value", "I");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(74, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ methodVisitor.visitFieldInsn(PUTFIELD, "A", "name", "Ljava/lang/String;");
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(75, label3);
+ methodVisitor.visitInsn(RETURN);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLocalVariable("this", "LA;", null, label0, label4, 0);
+ methodVisitor.visitLocalVariable("value", "I", null, label0, label4, 1);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label4, 2);
+ methodVisitor.visitMaxs(2, 3);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getValue", "()I", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(78, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "A", "value", "I");
+ methodVisitor.visitInsn(IRETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "getName", "()Ljava/lang/String;", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(82, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "A", "name", "Ljava/lang/String;");
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(67, label0);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitLdcInsn("FIRST");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(68, label1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_1", "LA;");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(69, label2);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitLdcInsn("SECOND");
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(70, label3);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_2", "LA;");
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(71, label4);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitLdcInsn("THIRD");
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(72, label5);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "A", "createA", "(ILjava/lang/String;)LA;", false);
+ methodVisitor.visitFieldInsn(PUTSTATIC, "A", "A_3", "LA;");
+ Label label6 = new Label();
+ methodVisitor.visitLabel(label6);
+ methodVisitor.visitLineNumber(71, label6);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
index 401f798..6e8f3e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/InconsistentPrefixTest.java
@@ -4,21 +4,33 @@
package com.android.tools.r8.desugar.desugaredlibrary;
-import static junit.framework.TestCase.assertTrue;
-import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
import com.android.tools.r8.jasmin.JasminBuilder;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class InconsistentPrefixTest extends TestBase {
- @Test
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public InconsistentPrefixTest(TestParameters parameters) {}
+
+ @Test(expected = CompilationFailedException.class)
public void testNoInconsistentPrefixes() throws Exception {
Map<String, String> x = new HashMap<>();
x.put("pkg.sub", "p$.bus");
@@ -30,18 +42,16 @@
Path inputJar = temp.getRoot().toPath().resolve("input.jar");
jasminBuilder.writeJar(inputJar);
- try {
- testForD8()
- .addProgramFiles(inputJar)
- .addOptionsModification(
- options ->
- options.desugaredLibraryConfiguration =
- DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x))
- .compile();
- fail("Should have raised the compilation error.");
- } catch (CompilationFailedException e) {
- assertTrue(
- e.getCause().getMessage().startsWith("Error: Inconsistent prefix in desugared library:"));
- }
+ testForD8()
+ .addProgramFiles(inputJar)
+ .addOptionsModification(
+ options ->
+ options.desugaredLibraryConfiguration =
+ DesugaredLibraryConfiguration.withOnlyRewritePrefixForTesting(x))
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("Inconsistent prefix in desugared library"));
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 867b61c..7f2fede 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -13,8 +13,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
@@ -68,12 +70,11 @@
testIncompleteNestError();
}
- private TestCompileResult<?, ?> compileOnlyClassesMatching(
+ private TestCompilerBuilder<?, ?, ?, ?, ?> compileOnlyClassesMatching(
Matcher<String> matcher,
boolean d8,
boolean allowDiagnosticWarningMessages,
- boolean ignoreMissingClasses)
- throws Exception {
+ boolean ignoreMissingClasses) {
List<Path> matchingClasses =
CLASS_NAMES.stream()
.filter(matcher::matches)
@@ -83,8 +84,7 @@
return testForD8()
.setMinApi(parameters.getApiLevel())
.addProgramFiles(matchingClasses)
- .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
- .compile();
+ .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true);
} else {
return testForR8(parameters.getBackend())
.noTreeShaking()
@@ -97,8 +97,7 @@
options.enableNestBasedAccessDesugaring = true;
options.ignoreMissingClasses = ignoreMissingClasses;
})
- .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages)
- .compile();
+ .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages);
}
}
@@ -106,27 +105,39 @@
try {
Matcher<String> innerClassMatcher =
containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
- compileOnlyClassesMatching(innerClassMatcher, false, false, false);
- fail("Should have raised an exception for missing nest host");
- } catch (Exception e) {
- assertTrue(e.getCause().getCause().getMessage().contains("requires its nest host"));
+ compileOnlyClassesMatching(innerClassMatcher, false, false, false)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorMessageThatMatches(containsString("requires its nest host"));
+ });
+ } catch (CompilationFailedException e) {
+ // Expected failure.
+ return;
}
+ fail("Should have raised an exception for missing nest host");
}
private void testIncompleteNestError() {
try {
Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
- compileOnlyClassesMatching(innerClassMatcher, false, false, false);
- fail("Should have raised an exception for incomplete nest");
+ compileOnlyClassesMatching(innerClassMatcher, false, false, false)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorMessageThatMatches(
+ containsString("requires its nest mates"));
+ });
} catch (Exception e) {
- assertTrue(e.getCause().getCause().getMessage().contains("requires its nest mates"));
+ // Expected failure.
+ return;
}
+ fail("Should have raised an exception for incomplete nest");
}
private void testMissingNestHostWarning(boolean d8, boolean desugarWarning) throws Exception {
Matcher<String> innerClassMatcher =
containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
- TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
+ TestCompileResult<?, ?> compileResult =
+ compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
if (desugarWarning) {
assertTrue(
@@ -143,7 +154,7 @@
private void testIncompleteNestWarning(boolean d8, boolean desugarWarning) throws Exception {
Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
TestCompileResult<?, ?> compileResult =
- compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
+ compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
if (desugarWarning) {
assertTrue(
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
new file mode 100644
index 0000000..1f19d89
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2020, 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.diagnostics;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ErrorDuringIrConversionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ static final Origin ORIGIN =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "<test-origin>";
+ }
+ };
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ErrorDuringIrConversionTest(TestParameters parameters) {}
+
+ private ThrowableConsumer<D8TestBuilder> addTestClassWithOrigin() {
+ return b ->
+ b.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(TestClass.class), ORIGIN);
+ }
+
+ private void checkCompilationFailedException(
+ CompilationFailedException e, Matcher<String> messageMatcher, Matcher<String> stackMatcher) {
+ // Check that the failure exception exiting the compiler contains origin info in the message.
+ assertThat(e.getMessage(), messageMatcher);
+ // Check that the stack trace has the version marker.
+ StringWriter writer = new StringWriter();
+ e.printStackTrace(new PrintWriter(writer));
+ assertThat(writer.toString(), stackMatcher);
+ }
+
+ private static void throwNPE() {
+ throw new NullPointerException("A test NPE");
+ }
+
+ @Test
+ public void testNPE() throws Exception {
+ try {
+ testForD8()
+ .apply(addTestClassWithOrigin())
+ .addOptionsModification(options -> options.testing.hookInIrConversion = () -> throwNPE())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ // Check that the error is reported as an error to the diagnostics handler.
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ allOf(
+ diagnosticOrigin(ORIGIN),
+ diagnosticException(NullPointerException.class),
+ diagnosticMessage(containsString("A test NPE"))));
+ });
+ } catch (CompilationFailedException e) {
+ checkCompilationFailedException(
+ e,
+ containsString(ORIGIN.toString()),
+ allOf(containsString("fakeStackEntry"), containsString("throwNPE")));
+ return;
+ }
+ fail("Expected compilation to fail");
+ }
+
+ @Test
+ public void testFatalError() throws Exception {
+ try {
+ testForD8()
+ .apply(addTestClassWithOrigin())
+ .addOptionsModification(
+ options ->
+ options.testing.hookInIrConversion =
+ () -> options.reporter.fatalError("My Fatal Error!"))
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ // Check that the error is reported as an error to the diagnostics handler.
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticMessage(containsString("My Fatal Error")),
+ // The fatal error is not given an origin, so it can't provide it.
+ // Note: This could be fixed by delaying reporting and associate the info
+ // at the top-level handler. It would require mangling of the diagnostic,
+ // so maybe not that elegant.
+ diagnosticOrigin(Origin.unknown())));
+ });
+ } catch (CompilationFailedException e) {
+ checkCompilationFailedException(
+ e, containsString(ORIGIN.toString()), containsString("fakeStackEntry"));
+ return;
+ }
+ fail("Expected compilation to fail");
+ }
+
+ private static void reportErrors(Reporter reporter) {
+ reporter.error("FOO!");
+ reporter.error("BAR!");
+ reporter.error("BAZ!");
+ }
+
+ @Test
+ public void testThreeErrors() throws Exception {
+ AtomicBoolean doError = new AtomicBoolean(true);
+ try {
+ testForD8()
+ .apply(addTestClassWithOrigin())
+ .addOptionsModification(
+ options ->
+ options.testing.hookInIrConversion =
+ () -> {
+ // Ensure that the errors are reported just once as IR conversion is
+ // threaded.
+ if (doError.getAndSet(false)) {
+ reportErrors(options.reporter);
+ }
+ })
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ // Check that the error is reported as an error to the diagnostics handler.
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsCount(3)
+ .assertAllErrorsMatch(
+ allOf(
+ diagnosticOrigin(Origin.unknown()),
+ diagnosticType(StringDiagnostic.class),
+ diagnosticMessage(
+ anyOf(
+ containsString("FOO!"),
+ containsString("BAR!"),
+ containsString("BAZ!")))));
+ });
+ } catch (CompilationFailedException e) {
+ checkCompilationFailedException(
+ e,
+ // There may be no fail-if-error barrier inside any origin association, thus only the
+ // top level message can be expected here.
+ containsString("Compilation failed to complete"),
+ // The stack trace must contain both the version, the frame for the hook above, and one
+ // of the error messages.
+ allOf(
+ containsString("fakeStackEntry"),
+ containsString("reportErrors"),
+ anyOf(containsString("FOO!"), containsString("BAR!"), containsString("BAZ!"))));
+ return;
+ }
+ fail("Expected compilation to fail");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
new file mode 100644
index 0000000..0d01ffe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StaticMethodsEnumUnboxingTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public StaticMethodsEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = StaticMethods.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(StaticMethodsEnumUnboxingTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
+ })
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public static void print(Object o) {
+ System.out.println(o);
+ }
+
+ @NeverInline
+ public static void printEnum(MyEnum e) {
+ System.out.println(e.ordinal());
+ }
+
+ @NeverInline
+ public static MyEnum returnEnum(boolean bool) {
+ return bool ? MyEnum.A : MyEnum.B;
+ }
+
+ @NeverInline
+ protected static void printProtected() {
+ System.out.println("protected");
+ }
+
+ @NeverInline
+ static void printPackagePrivate() {
+ System.out.println("package-private");
+ }
+
+ @NeverInline
+ private static void printPrivate() {
+ System.out.println("private");
+ }
+
+ @NeverInline
+ public static void callPrivate() {
+ System.out.print("call: ");
+ printPrivate();
+ }
+ }
+
+ // Use two enums to test collision between values and valueOf.
+ enum MyEnum2 {
+ A,
+ B,
+ C;
+ }
+
+ static class StaticMethods {
+
+ public static void main(String[] args) {
+ testCustomMethods();
+ testNonPublicMethods();
+ testGeneratedMethods();
+ testGeneratedMethods2();
+ }
+
+ @NeverInline
+ private static void testNonPublicMethods() {
+ MyEnum.printPrivate();
+ System.out.println("private");
+ MyEnum.printPackagePrivate();
+ System.out.println("package-private");
+ MyEnum.printProtected();
+ System.out.println("protected");
+ MyEnum.callPrivate();
+ System.out.println("call: private");
+ }
+
+ @NeverInline
+ private static void testCustomMethods() {
+ MyEnum.print("print");
+ System.out.println("print");
+ MyEnum.printEnum(MyEnum.A);
+ System.out.println(0);
+ System.out.println((MyEnum.returnEnum(true).ordinal()));
+ System.out.println(0);
+ }
+
+ @NeverInline
+ private static void testGeneratedMethods() {
+ System.out.println(MyEnum.valueOf("C").ordinal());
+ System.out.println(2);
+ System.out.println(MyEnum.values()[0].ordinal());
+ System.out.println(0);
+ }
+
+ @NeverInline
+ private static void testGeneratedMethods2() {
+ System.out.println(MyEnum2.valueOf("C").ordinal());
+ System.out.println(2);
+ System.out.println(MyEnum2.values()[0].ordinal());
+ System.out.println(0);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index 3ccdd37..dc2e217 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -50,15 +50,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.inspectDiagnosticMessages(
- m -> {
- // TODO(b/150370354): We need to allow static helper method to re-enable unboxing
- // with switches.
- if (enumValueOptimization && enumKeepRules.getKeepRule().equals("")) {
- assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
- } else {
- assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
- }
- })
+ m -> assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m))
.run(parameters.getRuntime(), classToTest)
.assertSuccess();
assertLines2By2Correct(run.getStdOut());
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 3b3473a..e9d9abd 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -45,7 +45,7 @@
protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
try {
return new ApplicationReader(input, options, Timing.empty()).read();
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index 41ce8a8..f46650f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.ir.analysis.type;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -72,7 +72,12 @@
.allowDiagnosticWarningMessages()
.enableMergeAnnotations()
.setMinApi(parameters.getApiLevel())
- .compile()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (!allowTypeErrors) {
+ diagnostics.assertErrorThatMatches(diagnosticException(AssertionError.class));
+ }
+ })
.assertAllWarningMessagesMatch(
equalTo(
"The method `void "
@@ -104,6 +109,7 @@
// locals: { 'java/lang/Object' }
// stack: { 'java/lang/Object' }
.assertFailureWithErrorThatMatches(containsString("NullPointerException"));
+
} catch (CompilationFailedException e) {
// Compilation should only fail when type errors are not allowed.
assertFalse(
@@ -111,9 +117,6 @@
"Test should only throw when type errors are not allowed",
Throwables.getStackTraceAsString(e)),
allowTypeErrors);
-
- // Verify that we fail with an assertion error.
- assertThat(e.getCause().getMessage(), containsString("AssertionError"));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B156470722.java b/src/test/java/com/android/tools/r8/ir/optimize/B156470722.java
new file mode 100644
index 0000000..57a7fb2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B156470722.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2020, 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.ir.optimize;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.ReprocessMethod;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class B156470722 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B156470722(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(B156470722.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableReprocessMethodAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ method("Unused");
+ method(" world!", "Unused");
+ }
+
+ @NeverInline
+ static void method(String unused) {
+ System.out.print("Hello");
+ }
+
+ @NeverInline
+ @ReprocessMethod
+ static void method(String used, String unused) {
+ System.out.println(used);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index e01ede5..626f771 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -156,7 +156,7 @@
MethodSubject checkViaCall = mainSubject.uniqueMethodWithName("checkViaCall");
assertThat(checkViaCall, isPresent());
assertEquals(0, countActCall(checkViaCall));
- assertEquals(2, countPrintCall(checkViaCall));
+ assertEquals(canSharePrintCallInSuccessorBlock() ? 1 : 2, countPrintCall(checkViaCall));
MethodSubject checkViaIntrinsic = mainSubject.uniqueMethodWithName("checkViaIntrinsic");
assertThat(checkViaIntrinsic, isPresent());
@@ -188,12 +188,7 @@
MethodSubject checkViaCall = mainSubject.uniqueMethodWithName("checkViaCall");
assertThat(checkViaCall, isPresent());
assertEquals(0, countActCall(checkViaCall));
- // With API level >= Q we get a register assignment that allows us to share the print call in a
- // successor block. See also InternalOptions.canHaveThisJitCodeDebuggingBug().
- boolean canSharePrintCallInSuccessorBlock =
- parameters.isDexRuntime()
- && parameters.getApiLevel().getLevel() >= AndroidApiLevel.Q.getLevel();
- assertEquals(canSharePrintCallInSuccessorBlock ? 1 : 2, countPrintCall(checkViaCall));
+ assertEquals(canSharePrintCallInSuccessorBlock() ? 1 : 2, countPrintCall(checkViaCall));
MethodSubject checkViaIntrinsic = mainSubject.uniqueMethodWithName("checkViaIntrinsic");
assertThat(checkViaIntrinsic, isPresent());
@@ -206,6 +201,13 @@
assertEquals(0, countThrow(checkAtOneLevelHigher));
}
+ private boolean canSharePrintCallInSuccessorBlock() {
+ // With API level >= Q we get a register assignment that allows us to share the print call in a
+ // successor block. See also InternalOptions.canHaveThisJitCodeDebuggingBug().
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().getLevel() >= AndroidApiLevel.Q.getLevel();
+ }
+
@Test
public void testNonNullParamAfterInvokeInterface() throws Exception {
Class<?> mainClass = NonNullParamAfterInvokeInterfaceMain.class;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index d474dad..8ff71b8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -124,7 +124,7 @@
o.inliningInstructionLimit = 6;
// Tests depend on nullability of receiver and argument in general. Learning very accurate
// nullability from actual usage in tests bothers what we want to test.
- o.enablePropagationOfDynamicTypesAtCallSites = false;
+ o.callSiteOptimizationOptions().disableTypePropagationForTesting();
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
index bdc3694..55daa59 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -52,7 +52,7 @@
o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("non-null")
.inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index e837610..cb8dfec 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -47,7 +47,7 @@
.addKeepMainRule(MAIN)
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
- .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
.addOptionsModification(
o -> {
// To prevent invoke-interface from being rewritten to invoke-virtual w/ a single
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
index a1e936b..3a00d96 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -45,11 +45,12 @@
.addInnerClasses(InvokeStaticPositiveTest.class)
.addKeepMainRule(MAIN)
.enableInliningAnnotations()
- .addOptionsModification(o -> {
- o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
- })
+ .addOptionsModification(
+ o -> {
+ o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+ })
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("non-null")
.inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index 30764845..91d181e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -16,7 +16,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -53,7 +53,7 @@
o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
})
.setMinApi(parameters.getApiLevel())
- .addOptionsModification(InternalOptions::enablePropagationOfConstantsAtCallSites)
+ .addOptionsModification(CallSiteOptimizationOptions::enableConstantPropagationForTesting)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines("non-null", "null")
.inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 7807a9f..0d81ff2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -150,7 +150,7 @@
// In `getMainClass`, a call with `null`, which will throw NPE, is replaced with null throwing
// code. Then, remaining call with non-null argument made getClass() replaceable.
// Disable the propagation of call site information to separate the tests.
- options.enablePropagationOfDynamicTypesAtCallSites = false;
+ options.callSiteOptimizationOptions().disableTypePropagationForTesting();
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index 0ac155c..0fe6e92 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -58,7 +58,7 @@
// This test wants to check if compile-time computation is not applied to non-null,
// non-constant value. In a simple test setting, call-site optimization knows the argument is
// always a non-null, specific constant, but that is beyond the scope of this test.
- options.enablePropagationOfConstantsAtCallSites = false;
+ assert !options.callSiteOptimizationOptions().isConstantPropagationEnabled();
}
private void test(TestRunResult result, int expectedStringIsEmptyCount) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index c4dbbc4..0333284 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -58,7 +58,7 @@
// Disable the propagation of call site information to test String#valueOf optimization with
// nullable argument. Otherwise, e.g., we know that only `null` is used for `hideNPE`, and then
// simplify everything in that method.
- options.enablePropagationOfDynamicTypesAtCallSites = false;
+ options.callSiteOptimizationOptions().disableTypePropagationForTesting();
options.testing.forceNameReflectionOptimization = true;
}
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index 3028525..d21bd3a 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -311,6 +311,7 @@
}
private void skipIfNeeded(String test, Tool tool) {
+ Assume.assumeFalse("Triage (b/144966342)", getDexVm().isNewerThan(DexVm.ART_9_0_0_HOST));
// Is it part of smoke tests ?
if (!RUN_ALL_TESTS) {
Assume.assumeTrue("Skipping non-smoke test " + test, SMOKE_TESTS.contains(test));
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
index 4062cb0..bf17ac0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.kotlin.lambda;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assume.assumeTrue;
@@ -60,10 +59,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
// TODO(b/143165163): better not output info like this.
- .assertAllInfoMessagesMatch(
- allOf(
- containsString("Unrecognized Kotlin lambda"),
- containsString("unexpected static method")))
+ .assertAllInfoMessagesMatch(containsString("unexpected static method"))
.addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
.run(parameters.getRuntime(), pkg + ".B143165163Kt")
.assertSuccessWithOutputLines("outer foo bar", "outer foo default");
@@ -89,10 +85,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
// TODO(b/143165163): better not output info like this.
- .assertAllInfoMessagesMatch(
- allOf(
- containsString("Unrecognized Kotlin lambda"),
- containsString("does not implement any interfaces")))
+ .assertAllInfoMessagesMatch(containsString("does not implement any interfaces"))
.assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
.run(parameters.getRuntime(), pkg + ".B143165163Kt")
.assertSuccessWithOutputLines("outer foo bar", "outer foo default");
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
new file mode 100644
index 0000000..f4ee57a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteAnonymousTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED = "foo";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteAnonymousTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String baseLibFolder = PKG_PREFIX + "/anonymous_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/anonymous_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".anonymous_app.MainKt")
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addKeepAllClassesRuleWithAllowObfuscation()
+ .addKeepRules("-keep class " + PKG + ".anonymous_lib.Test$A { *; }")
+ .addKeepRules("-keep class " + PKG + ".anonymous_lib.Test { *; }")
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+ Path main =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/anonymous_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(main)
+ .run(parameters.getRuntime(), PKG + ".anonymous_app.MainKt")
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(PKG + ".anonymous_lib.Test");
+ System.out.println(
+ KotlinMetadataWriter.kotlinMetadataToString("", clazz.getKotlinClassMetadata()));
+ ClassSubject anonymousClass = inspector.clazz(PKG + ".anonymous_lib.Test$internalProp$1");
+ assertThat(anonymousClass, isRenamed());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
index bc0f2a4..06d2636 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
@@ -104,6 +104,7 @@
public void testMetadataForLib() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(libJars.get(targetVersion))
.addKeepAllClassesRule()
.addKeepAttributes(
@@ -158,6 +159,7 @@
public void testMetadataForReflect() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(libJars.get(targetVersion))
.addKeepAllClassesRule()
.addKeepAttributes(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
new file mode 100644
index 0000000..a8fcf61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteDependentKeep extends KotlinMetadataTestBase {
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ private final TestParameters parameters;
+
+ public MetadataRewriteDependentKeep(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws CompilationFailedException, IOException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepKotlinMetadata()
+ .addKeepRules(StringUtils.joinLines("-if class *.Metadata", "-keep class <1>.io.** { *; }"))
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // All kept classes should have their kotlin metadata.
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.getFinalName().startsWith("kotlin.io")
+ || clazz.getFinalName().equals("kotlin.Metadata")) {
+ assertNotNull(clazz.getKotlinClassMetadata());
+ } else {
+ assertNull(clazz.getKotlinClassMetadata());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
new file mode 100644
index 0000000..90b74f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
@@ -0,0 +1,142 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteFlexibleUpperBoundTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED = StringUtils.lines("B.foo(): 42");
+ private final String PKG_LIB = PKG + ".flexible_upper_bound_lib";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteFlexibleUpperBoundTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String baseLibFolder = PKG_PREFIX + "/flexible_upper_bound_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/flexible_upper_bound_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".flexible_upper_bound_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(libJars.get(targetVersion))
+ // Allow renaming A to ensure that we rename in the flexible upper bound type.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".A { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".B { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".FlexibleUpperBound { *; }")
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+ Path main =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/flexible_upper_bound_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(main)
+ .run(parameters.getRuntime(), PKG + ".flexible_upper_bound_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) throws IOException, ExecutionException {
+ // We are checking that A is renamed, and that the flexible upper bound information is
+ // reflecting that.
+ ClassSubject a = inspector.clazz(PKG_LIB + ".A");
+ assertThat(a, isRenamed());
+
+ ClassSubject flexibleUpperBound = inspector.clazz(PKG_LIB + ".FlexibleUpperBound");
+ assertThat(flexibleUpperBound, isPresent());
+ assertThat(flexibleUpperBound, not(isRenamed()));
+
+ List<KmPropertySubject> properties = flexibleUpperBound.getKmClass().getProperties();
+ assertEquals(1, properties.size());
+ KmPropertySubject kmPropertySubject = properties.get(0);
+ KmTypeSubject returnTypeSubject = kmPropertySubject.returnType();
+ assertThat(returnTypeSubject, isPresent());
+ assertEquals(1, returnTypeSubject.typeArguments().size());
+ KmTypeProjectionSubject argumentSubject = returnTypeSubject.typeArguments().get(0);
+ KmFlexibleTypeUpperBound flexUpperBound = argumentSubject.type().getFlexibleUpperBound();
+ assertNotNull(flexUpperBound);
+ assertEquals(
+ "Class(name=" + a.getFinalBinaryName() + ")",
+ flexUpperBound.getType().classifier.toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index 4e5ae0e..4aa0a2c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -9,7 +9,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
@@ -92,74 +91,11 @@
}
@Test
- public void testMetadataInClasspathType_merged() throws Exception {
- Path baseLibJar = baseLibJarMap.get(targetVersion);
- Path libJar =
- testForR8(parameters.getBackend())
- .addClasspathFiles(baseLibJar)
- .addProgramFiles(extLibJarMap.get(targetVersion))
- // Keep the Extra class and its interface (which has the method).
- .addKeepRules("-keep class **.Extra")
- // Keep the ImplKt extension method which requires metadata
- // to be called with Kotlin syntax from other kotlin code.
- .addKeepRules("-keep class **.ImplKt { <methods>; }")
- .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
- .compile()
- .inspect(this::inspectMerged)
- .writeToZip();
-
- Path output =
- kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
- .addClasspathFiles(baseLibJar, libJar)
- .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/classpath_app", "main"))
- .setOutputPath(temp.newFolder().toPath())
- .compile();
-
- testForJvm()
- .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), baseLibJar, libJar)
- .addClasspath(output)
- .run(parameters.getRuntime(), PKG + ".classpath_app.MainKt")
- .assertSuccessWithOutput(EXPECTED);
- }
-
- private void inspectMerged(CodeInspector inspector) {
- String implClassName = PKG + ".classpath_lib_ext.Impl";
- String implKtClassName = PKG + ".classpath_lib_ext.ImplKt";
- String extraClassName = PKG + ".classpath_lib_ext.Extra";
-
- assertThat(inspector.clazz(implClassName), not(isPresent()));
-
- ClassSubject implKt = inspector.clazz(implKtClassName);
- assertThat(implKt, isPresent());
- assertThat(implKt, not(isRenamed()));
- // API entry is kept, hence the presence of Metadata.
- KmPackageSubject kmPackage = implKt.getKmPackage();
- assertThat(kmPackage, isPresent());
-
- KmFunctionSubject kmFunction = kmPackage.kmFunctionExtensionWithUniqueName("fooExt");
- assertThat(kmFunction, isPresent());
-
- ClassSubject extra = inspector.clazz(extraClassName);
- assertThat(extra, isPresent());
- assertThat(extra, not(isRenamed()));
- // API entry is kept, hence the presence of Metadata.
- KmClassSubject kmClass = extra.getKmClass();
- assertThat(kmClass, isPresent());
- List<ClassSubject> superTypes = kmClass.getSuperTypes();
- assertTrue(superTypes.stream().noneMatch(
- supertype -> supertype.getFinalDescriptor().contains("Impl")));
- // The super types are changed and we should not keep any information about it in the metadata.
- List<String> superTypeDescriptors = kmClass.getSuperTypeDescriptors();
- assertEquals(1, superTypeDescriptors.size());
- assertEquals(KT_ANY, superTypeDescriptors.get(0));
- }
-
- @Test
public void testMetadataInClasspathType_renamed() throws Exception {
Path baseLibJar = baseLibJarMap.get(targetVersion);
Path libJar =
testForR8(parameters.getBackend())
- .addClasspathFiles(baseLibJar)
+ .addClasspathFiles(baseLibJar, ToolHelper.getKotlinStdlibJar())
.addProgramFiles(extLibJarMap.get(targetVersion))
// Keep the Extra class and its interface (which has the method).
.addKeepRules("-keep class **.Extra")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index 9d75aaa..38a6815 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -93,6 +93,7 @@
public void testMetadataInCompanion_kept() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(companionLibJarMap.get(targetVersion))
// Keep everything
.addKeepRules("-keep class **.companion_lib.** { *; }")
@@ -124,6 +125,7 @@
public void testMetadataInCompanion_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(companionLibJarMap.get(targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
@@ -134,8 +136,8 @@
.addKeepRules("-keepclassmembers class **.B$* { *** get*(...); *** set*(...); }")
// Keep the companion instance in the B class
.addKeepRules("-keepclassmembers class **.B { *** Companion; }")
- // Keep the name of companion class
- .addKeepRules("-keepnames class **.*$Companion")
+ // Keep the class of the companion class.
+ .addKeepRules("-keep class **.*$Companion")
// No rule for Super, but will be kept and renamed.
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
// To keep @JvmField annotation
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index aaf2674..41d1f09 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -143,6 +143,7 @@
public void testMetadataInExtensionFunction_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(extLibJarMap.get(targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index 1606cbe..c627171 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -152,6 +152,7 @@
public void testMetadataInExtensionProperty_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(extLibJarMap.get(targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index 60cad3b..7cfa4a7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -149,6 +149,7 @@
public void testMetadataInFunction_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(funLibJarMap.get(targetVersion))
// Keep the B class and its interface (which has the doStuff method).
.addKeepRules("-keep class **.B")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index fea7b3e..d82b5c6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -90,6 +90,7 @@
public void testMetadataInFunctionWithVararg() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(varargLibJarMap.get(targetVersion))
// keep SomeClass#foo, since there is a method reference in the app.
.addKeepRules("-keep class **.SomeClass { *** foo(...); }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index 71dd5df..12b361b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -92,7 +92,8 @@
String main = PKG + ".libtype_app.MainKt";
Path out =
testForR8(parameters.getBackend())
- // Intentionally not providing basLibJar as lib file nor classpath file.
+ // Intentionally not providing baseLibJar as lib file nor classpath file.
+ .addClasspathFiles()
.addProgramFiles(extLibJarMap.get(targetVersion), appJarMap.get(targetVersion))
// Keep Ext extension method which requires metadata to be called with Kotlin syntax
// from other kotlin code.
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 477c5e6..09d62d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -95,6 +95,7 @@
public void testMetadataInMultifileClass_merged() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(multifileLibJarMap.get(targetVersion))
// Keep UtilKt#comma*Join*(). Let R8 optimize (inline) others, such as joinOf*(String).
.addKeepRules("-keep class **.UtilKt")
@@ -129,21 +130,22 @@
assertThat(joinOfInt, not(isPresent()));
inspectMetadataForFacade(inspector, util);
-
- inspectSignedKt(inspector);
+ // TODO(b/156290332): Seems like this test is incorrect and should never work.
+ // inspectSignedKt(inspector);
}
@Test
public void testMetadataInMultifileClass_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(multifileLibJarMap.get(targetVersion))
// Keep UtilKt#comma*Join*().
.addKeepRules("-keep class **.UtilKt")
+ .addKeepRules("-keep,allowobfuscation class **.UtilKt__SignedKt")
.addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
// Keep yet rename joinOf*(String).
- .addKeepRules(
- "-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
+ .addKeepRules("-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
.inspect(this::inspectRenamed)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
index f4530b3..3c218fe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInNestedClassTest.java
@@ -84,6 +84,7 @@
public void testMetadataInNestedClass() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(nestedLibJarMap.get(targetVersion))
// Keep the Outer class and delegations.
.addKeepRules("-keep class **.Outer { <init>(...); *** delegate*(...); }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
index 54cd15c..53c8e4f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -82,6 +82,7 @@
public void testMetadataInParameterType_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(parameterTypeLibJarMap.get(targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep public class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index 3847134..f97fd1c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -88,6 +88,7 @@
public void testMetadataInProperty_getterOnly() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(propertyTypeLibJarMap.get(targetVersion))
// Keep property getters
.addKeepRules("-keep class **.Person { <init>(...); }")
@@ -180,6 +181,7 @@
public void testMetadataInProperty_setterOnly() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(propertyTypeLibJarMap.get(targetVersion))
// Keep property setters (and users)
.addKeepRules("-keep class **.Person { <init>(...); }")
@@ -243,9 +245,9 @@
// # fieldSignature: name:Ljava/lang/String;,
// # getterSignature: getName()Ljava/lang/String;,
// # setterSignature: setName(Ljava/lang/String;)V,
- assertEquals(name.fieldSignature().asString(), "name:Ljava/lang/String;");
- assertEquals(name.getterSignature().asString(), "getName()Ljava/lang/String;");
- assertEquals(name.setterSignature().asString(), "setName(Ljava/lang/String;)V");
+ assertEquals("name:Ljava/lang/String;", name.fieldSignature().asString());
+ assertEquals("getName()Ljava/lang/String;", name.getterSignature().asString());
+ assertEquals("setName(Ljava/lang/String;)V", name.setterSignature().asString());
KmPropertySubject familyName = kmClass.kmPropertyWithUniqueName("familyName");
assertThat(familyName, not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
index ceced0d..dea1c4a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -82,6 +82,7 @@
public void testMetadataInProperty_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(propertyTypeLibJarMap.get(targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep public class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
index 09d4a5a..63fc38d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -82,6 +82,7 @@
public void testMetadataInReturnType_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(returnTypeLibJarMap.get(targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep public class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index b09ef7e..318a765 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -14,7 +14,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
@@ -90,6 +89,7 @@
public void testMetadataInSealedClass_valid() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(sealedLibJarMap.get(targetVersion))
// Keep the Expr class
.addKeepRules("-keep class **.Expr")
@@ -155,6 +155,7 @@
public void testMetadataInSealedClass_invalid() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(sealedLibJarMap.get(targetVersion))
// Keep the Expr class
.addKeepRules("-keep class **.Expr")
@@ -180,14 +181,8 @@
private void inspectInvalid(CodeInspector inspector) {
String exprClassName = PKG + ".sealed_lib.Expr";
- String numClassName = PKG + ".sealed_lib.Num";
String libClassName = PKG + ".sealed_lib.LibKt";
- // Without any specific keep rule and no instantiation point, it's not necessary to keep
- // sub classes of Expr.
- ClassSubject num = inspector.clazz(numClassName);
- assertThat(num, not(isPresent()));
-
ClassSubject expr = inspector.clazz(exprClassName);
assertThat(expr, isPresent());
assertThat(expr, not(isRenamed()));
@@ -195,8 +190,6 @@
KmClassSubject kmClass = expr.getKmClass();
assertThat(kmClass, isPresent());
- assertTrue(kmClass.getSealedSubclassDescriptors().isEmpty());
-
ClassSubject libKt = inspector.clazz(libClassName);
assertThat(expr, isPresent());
assertThat(expr, not(isRenamed()));
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
index 3c02dac..8eb76bb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSuperTypeTest.java
@@ -84,6 +84,7 @@
public void testMetadataInSupertype_merged() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(superTypeLibJarMap.get(targetVersion))
// Keep non-private members except for ones in `internal` definitions.
.addKeepRules("-keep public class !**.internal.**, * { !private *; }")
@@ -129,6 +130,7 @@
public void testMetadataInSupertype_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(superTypeLibJarMap.get(targetVersion))
// Keep non-private members except for ones in `internal` definitions.
.addKeepRules("-keep public class !**.internal.**, * { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 26ea8de..b2e0b8a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -112,6 +112,7 @@
public void testMetadataInTypeAlias_renamed() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(typeAliasLibJarMap.get(targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep class **.Impl { !private *; }")
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index bed423f..df35c97 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -16,7 +15,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -129,6 +127,7 @@
public void testMetadataInTypeAliasWithR8() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
.addProgramFiles(jarMap.get(targetVersion))
// Keep ClassThatWillBeObfuscated, but allow minification.
.addKeepRules("-keep,allowobfuscation class **ClassThatWillBeObfuscated")
@@ -148,20 +147,16 @@
.compile()
.inspect(this::inspect)
.writeToZip();
-
- // TODO(b/152306391): Reified type-parameters are not flagged correctly.
- ProcessResult mainResult =
+ Path mainJar =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typeargument_app", "main"))
- .setOutputPath(temp.newFolder().toPath())
- .compileRaw();
- assertEquals(1, mainResult.exitCode);
- assertThat(
- mainResult.stderr,
- containsString(
- "org.jetbrains.kotlin.codegen.CompilationException: "
- + "Back-end (JVM) Internal error: wrong bytecode generated"));
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(mainJar)
+ .run(parameters.getRuntime(), PKG + ".typeargument_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
}
private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
new file mode 100644
index 0000000..ffe5a51
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import junit.framework.TestCase;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInlinePropertyTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED = StringUtils.lines("true", "false", "false", "true");
+ private final String PKG_LIB = PKG + ".inline_property_lib";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteInlinePropertyTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String baseLibFolder = PKG_PREFIX + "/inline_property_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/inline_property_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".inline_property_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(libJars.get(targetVersion))
+ // Allow renaming A to ensure that we rename in the flexible upper bound type.
+ .addKeepAllClassesRule()
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+ Path main =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/inline_property_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(main)
+ .run(parameters.getRuntime(), PKG + ".inline_property_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) throws IOException, ExecutionException {
+ CodeInspector stdLibInspector = new CodeInspector(libJars.get(targetVersion));
+ for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
+ ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
+ assertThat(r8Clazz, isPresent());
+ KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+ KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
+ if (originalMetadata == null) {
+ assertNull(rewrittenMetadata);
+ continue;
+ }
+ TestCase.assertNotNull(rewrittenMetadata);
+ KotlinClassHeader originalHeader = originalMetadata.getHeader();
+ KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
+ TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
+ TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+ // We cannot assert equality of the data since it may be ordered differently. Instead we use
+ // the KotlinMetadataWriter.
+ String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+ String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+ TestCase.assertEquals(expected, actual);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
new file mode 100644
index 0000000..bb61374
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteKeepPathTest extends KotlinMetadataTestBase {
+
+ @Parameterized.Parameters(name = "{0} target: {1}, keep: {2}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(),
+ KotlinTargetVersion.values(),
+ BooleanUtils.values());
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private static final String LIB_CLASS_NAME = PKG + ".box_primitives_lib.Test";
+ private final TestParameters parameters;
+ private final boolean keepMetadata;
+
+ public MetadataRewriteKeepPathTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion, boolean keepMetadata) {
+ super(targetVersion);
+ this.parameters = parameters;
+ this.keepMetadata = keepMetadata;
+ }
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String baseLibFolder = PKG_PREFIX + "/box_primitives_lib";
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(getKotlinFileInTest(baseLibFolder, "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void testProgramPath() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepRules("-keep class " + LIB_CLASS_NAME)
+ .applyIf(keepMetadata, TestShrinkerBuilder::addKeepKotlinMetadata)
+ .addKeepRuntimeVisibleAnnotations()
+ .allowDiagnosticWarningMessages()
+ .compile()
+ .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .inspect(inspector -> inspect(inspector, keepMetadata));
+ }
+
+ @Test
+ public void testClassPathPath() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addKeepRules("-keep class " + LIB_CLASS_NAME)
+ .addKeepRuntimeVisibleAnnotations()
+ .compile()
+ .inspect(inspector -> inspect(inspector, true));
+ }
+
+ @Test
+ public void testLibraryPath() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addKeepRules("-keep class " + LIB_CLASS_NAME)
+ .addKeepRuntimeVisibleAnnotations()
+ .compile()
+ .inspect(inspector -> inspect(inspector, true));
+ }
+
+ @Test
+ public void testMissing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addKeepRules("-keep class " + LIB_CLASS_NAME)
+ .addKeepRuntimeVisibleAnnotations()
+ .compile()
+ .inspect(inspector -> inspect(inspector, true));
+ }
+
+ private void inspect(CodeInspector inspector, boolean expectMetadata) {
+ ClassSubject clazz = inspector.clazz(LIB_CLASS_NAME);
+ assertThat(clazz, isPresent());
+ assertEquals(expectMetadata, clazz.getKotlinClassMetadata() != null);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
new file mode 100644
index 0000000..295f26a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteKeepTest extends KotlinMetadataTestBase {
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ private final TestParameters parameters;
+
+ public MetadataRewriteKeepTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepKotlinMetadata()
+ .addKeepRules("-keep class kotlin.io.** { *; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testR8KeepPartial() throws Exception {
+ // This test is a bit weird, since it shows that we can remove params from the kotlin.Metadata
+ // class, but still be able to fully read the kotlin.Metadata.
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class kotlin.Metadata { *** d1(); }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(
+ inspector -> {
+ inspect(inspector);
+ ClassSubject kotlinMetadataClass = inspector.clazz("kotlin.Metadata");
+ assertThat(kotlinMetadataClass, isPresent());
+ assertEquals(1, kotlinMetadataClass.allMethods().size());
+ assertNotNull(kotlinMetadataClass.getKmClass().getName());
+ });
+ }
+
+ @Test
+ public void testR8KeepPartialCooking() throws Exception {
+ // This test is a bit weird, since it shows that we can remove params from the kotlin.Metadata
+ // class, but still be able to fully read the kotlin.Metadata externally.
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class kotlin.Metadata { *** d1(); }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(
+ inspector -> {
+ inspect(inspector);
+ ClassSubject kotlinMetadataClass = inspector.clazz("kotlin.Metadata");
+ assertThat(kotlinMetadataClass, isPresent());
+ assertEquals(1, kotlinMetadataClass.allMethods().size());
+ assertNotNull(kotlinMetadataClass.getKmClass().getName());
+ });
+ }
+
+ @Test
+ public void testR8KeepIf() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class kotlin.io.** { *; }")
+ .addKeepRules("-if class * { *** $VALUES; }", "-keep class kotlin.Metadata { *; }")
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // All kept classes should have their kotlin metadata.
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.getFinalName().startsWith("kotlin.io")
+ || clazz.getFinalName().equals("kotlin.Metadata")) {
+ assertNotNull(clazz.getKotlinClassMetadata());
+ assertNotNull(clazz.getKotlinClassMetadata().getHeader().getData2());
+ } else {
+ assertNull(clazz.getKotlinClassMetadata());
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 4826e1a..a17f7dc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -50,6 +50,7 @@
.addProgramFiles(ToolHelper.getKotlinStdlibJar())
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
+ .addKeepKotlinMetadata()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
.inspect(this::inspect);
@@ -71,16 +72,12 @@
KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
// TODO(b/154199572): Should we check for meta-data version?
- // TODO(b/156290606): Check if we should assert the package names are equal.
- // assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+ assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
// We cannot assert equality of the data since it may be ordered differently. Instead we use
// the KotlinMetadataWriter.
String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- // TODO(b/155534905): For invalid synthetic class lambdas, we emit null after rewriting.
- if (clazzSubject.getKotlinClassMetadata().getHeader().getKind() != 3) {
- assertEquals(expected, actual);
- }
+ assertEquals(expected, actual);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index aa0fcf8..043a48a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -49,10 +49,8 @@
.addProgramFiles(getJavaJarFile(folder))
.addProgramFiles(ToolHelper.getKotlinReflectJar())
.addKeepMainRule(mainClassName)
+ .addKeepKotlinMetadata()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
- .addKeepRules("-keep class kotlin.Metadata")
- // TODO(b/151194540): if this option is settled down, this test is meaningless.
- .addOptionsModification(o -> o.enableKotlinMetadataRewritingForRenamedClasses = false)
.allowDiagnosticWarningMessages()
.setMinApi(parameters.getApiLevel())
.compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_app/main.kt
new file mode 100644
index 0000000..14cd0b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.kotlin.metadata.anonymous_app
+
+import com.android.tools.r8.kotlin.metadata.anonymous_lib.Test
+
+fun main() {
+ println(Test().prop.foo())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_lib/lib.kt
new file mode 100644
index 0000000..70d6668
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/anonymous_lib/lib.kt
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, 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.kotlin.metadata.anonymous_lib
+
+class Test {
+
+ abstract class A {
+ abstract fun foo() : String;
+ }
+
+ private val internalProp = object : A() {
+ override fun foo(): String {
+ return "foo";
+ }
+ }
+
+ val prop = internalProp
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_app/main.kt
new file mode 100644
index 0000000..9c56878
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_app/main.kt
@@ -0,0 +1,14 @@
+// Copyright (c) 2020, 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.kotlin.metadata.flexible_upper_bound_app
+
+import com.android.tools.r8.kotlin.metadata.flexible_upper_bound_lib.B
+import com.android.tools.r8.kotlin.metadata.flexible_upper_bound_lib.FlexibleUpperBound
+
+fun main() {
+ val flexible = FlexibleUpperBound(B())
+ flexible.ref.get().foo(42)
+ flexible.ref.set(null)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_lib/lib.kt
new file mode 100644
index 0000000..9dbb382
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/flexible_upper_bound_lib/lib.kt
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, 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.kotlin.metadata.flexible_upper_bound_lib
+
+open class A<T> {
+
+ open fun foo(t : T) {
+ println("A.foo(): " + t.toString())
+ }
+}
+
+class B : A<Int>() {
+
+ override fun foo(t : Int) {
+ println("B.foo(): " + t)
+ }
+}
+
+class FlexibleUpperBound<T> constructor(element: A<T>) {
+ var ref = java.util.concurrent.atomic.AtomicReference(element)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_app/main.kt
new file mode 100644
index 0000000..3f7acc6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_app/main.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.kotlin.metadata.inline_property_app
+
+import com.android.tools.r8.kotlin.metadata.inline_property_lib.Lib
+import com.android.tools.r8.kotlin.metadata.inline_property_lib.is7
+
+fun main() {
+ println(Lib(42).is42)
+ println(Lib(42).is7)
+ println(Lib(7).is42)
+ println(Lib(7).is7)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_lib/lib.kt
new file mode 100644
index 0000000..0636795
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/inline_property_lib/lib.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.kotlin.metadata.inline_property_lib
+
+class Lib(val number : Int) {
+
+ val is42
+ inline get() = number == 42;
+}
+
+class LibExt
+ val Lib.is7
+ inline get() = number == 7;
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
index 18badf8..5731086 100644
--- a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.kotlin.sealed;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
import static org.hamcrest.CoreMatchers.containsString;
@@ -74,10 +75,10 @@
.allowDiagnosticWarningMessages(parameters.isCfRuntime())
.addKeepMainRule(MAIN)
.compileWithExpectedDiagnostics(
- diagnosticMessages -> {
- diagnosticMessages.assertAllWarningMessagesMatch(
- containsString("Resource 'META-INF/MANIFEST.MF' already exists."));
- })
+ diagnostics ->
+ diagnostics.assertAllWarningsMatch(
+ diagnosticMessage(
+ containsString("Resource 'META-INF/MANIFEST.MF' already exists."))))
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutputLines(EXPECTED);
}
diff --git a/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
index 9d4c4fb..553fe0c 100644
--- a/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ClassNameMinifierOriginalClassNameTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -98,14 +99,15 @@
.addApplyMapping(libraryCompileResult.getProguardMap())
.allowDiagnosticWarningMessages()
.compileWithExpectedDiagnostics(
- diagnosticMessages ->
- diagnosticMessages.assertAllWarningMessagesMatch(
- containsString(
- "'"
- + B.class.getTypeName()
- + "' cannot be mapped to '"
- + A.class.getTypeName()
- + "' because it is in conflict"))));
+ diagnostics ->
+ diagnostics.assertAllWarningsMatch(
+ diagnosticMessage(
+ containsString(
+ "'"
+ + B.class.getTypeName()
+ + "' cannot be mapped to '"
+ + A.class.getTypeName()
+ + "' because it is in conflict")))));
}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
index b16347d..6804b54 100644
--- a/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/DontUseMixedCaseClassNamesExistingClassTest.java
@@ -51,7 +51,7 @@
.addKeepClassRulesWithAllowObfuscation(A.class)
.addKeepMainRule(Main.class)
.addKeepRules("-classobfuscationdictionary " + dictionary.toString())
- .ifTrue(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
+ .applyIf(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(EXPECTED)
.inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 39cf142..73d9085 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -5,8 +5,8 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.TestParameters;
@@ -14,6 +14,9 @@
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,6 +53,7 @@
.addProgramFiles(getKotlinJarFile(FOLDER))
.addProgramFiles(getJavaJarFile(FOLDER))
.addKeepMainRule(MAIN_CLASS_NAME)
+ .addKeepClassRulesWithAllowObfuscation(ENUM_CLASS_NAME)
.allowDiagnosticWarningMessages()
.minification(minify)
.setMinApi(parameters.getApiLevel())
@@ -59,7 +63,11 @@
.run(parameters.getRuntime(), MAIN_CLASS_NAME)
.inspector();
ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
- // TODO(b/156340144): Check why the ENUM_CLASS_NAME is not present.
- assertThat(enumClass, not(isPresent()));
+ assertThat(enumClass, isPresent());
+ assertEquals(minify, enumClass.isRenamed());
+ MethodSubject clinit = enumClass.clinit();
+ assertThat(clinit, isPresent());
+ assertEquals(
+ 0, Streams.stream(clinit.iterateInstructions(InstructionSubject::isThrow)).count());
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
index a55ef4b..f662aa6 100644
--- a/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b155249069/DontUseMixedCaseClassNamesExistingClassPackageTest.java
@@ -54,7 +54,7 @@
.addKeepClassRulesWithAllowObfuscation(A.class)
.addKeepMainRule(Main.class)
.addKeepRules("-packageobfuscationdictionary " + packageDictionary.toString())
- .ifTrue(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
+ .applyIf(dontUseMixedCase, b -> b.addKeepRules("-dontusemixedcaseclassnames"))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("A.A.foo()", "package_b.B.foo()")
.inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index 7509d3d..a29c664 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -161,7 +161,7 @@
@Test
public void test_keepNonPublic() throws Exception {
Assume.assumeFalse(shrinker.generatesDex() && vmVersionIgnored());
- Class mainClass = TestMain.class;
+ Class<?> mainClass = TestMain.class;
String keep = !minify ? "-keep" : "-keep,allowobfuscation";
List<String> config = ImmutableList.of(
"-printmapping",
diff --git a/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java b/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
index 520e1d8..8e0213a 100644
--- a/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/rules/NegatedClassMemberTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.proguard.rules;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -11,6 +12,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
@@ -21,15 +23,13 @@
@RunWith(Parameterized.class)
public class NegatedClassMemberTest extends TestBase {
- private final TestParameters parameters;
-
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public NegatedClassMemberTest(TestParameters parameters) {
- this.parameters = parameters;
+
}
@Test
@@ -41,7 +41,6 @@
NegatedClassMemberTestClassB.class,
NegatedClassMemberTestClassC.class)
.addKeepRules(getKeepRule())
- .setMinApi(parameters.getRuntime())
.compile();
// For some reason, Proguard fails with "The output jar is empty". One likely explanation is
@@ -62,27 +61,30 @@
NegatedClassMemberTestClassB.class,
NegatedClassMemberTestClassC.class)
.addKeepRules(getKeepRule())
- .compile();
- fail("Expected R8 to fail during parsing of the Proguard configuration file");
+ .compileWithExpectedDiagnostics(this::checkDiagnostics);
} catch (CompilationFailedException e) {
- int expectedOffset = getKeepRule().indexOf("!");
- int expectedColumn = expectedOffset + 1;
- assertThat(
- e.getCause().getMessage(),
- allOf(
- containsString(
- "Error: offset: "
- + expectedOffset
- + ", line: 1, column: "
- + expectedColumn
- + ", Unexpected character '!': "
- + "The negation character can only be used to negate access flags"),
- containsString(
- StringUtils.join(
- "\n",
- "-keepclasseswithmembers class ** { long x; !long y; }",
- " ^"))));
+ return;
}
+ fail("Expected R8 to fail during parsing of the Proguard configuration file");
+ }
+
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics) {
+ int expectedOffset = getKeepRule().indexOf("!");
+ int expectedColumn = expectedOffset + 1;
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ diagnosticMessage(
+ allOf(
+ containsString(":1:" + expectedColumn),
+ containsString(
+ "Unexpected character '!': "
+ + "The negation character can only be used to negate access flags"),
+ containsString(
+ StringUtils.join(
+ "\n",
+ "-keepclasseswithmembers class ** { long x; !long y; }",
+ " ^")))));
}
private String getKeepRule() {
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
index 4c2fedc..5d06942 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.AddIntLit8;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.Instruction;
@@ -25,6 +26,7 @@
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -87,6 +89,9 @@
inspector.method(TestClass.class.getMethod("method")).getMethod().getCode().asDexCode();
// Computation of k is constant folded and the value takes up one register. System.out takes
// up another register and the receiver is the last.
+ Assume.assumeTrue(
+ "TODO(b/144966342): Why 2 on Q?",
+ ToolHelper.getDexVm().isOlderThanOrEqual(ToolHelper.DexVm.ART_9_0_0_HOST));
assertEquals(3, code.registerSize);
checkNoLocals(code);
}
diff --git a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
index 9d633f2..7e56259 100644
--- a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
+++ b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
@@ -62,14 +62,14 @@
}
private void checkArt(TestCompileResult result) throws IOException {
- ProcessResult artResult = runOnArtRaw(
- result.app,
- TestClass.class.getCanonicalName(),
- builder -> {
- builder.appendArtOption("-Xusejit:true");
- },
- DexVm.ART_9_0_0_HOST
- );
+ ProcessResult artResult =
+ runOnArtRaw(
+ result.app,
+ TestClass.class.getCanonicalName(),
+ builder -> {
+ builder.appendArtOption("-Xusejit:true");
+ },
+ DexVm.ART_10_0_0_HOST);
assertEquals(0, artResult.exitCode);
assertFalse(artResult.stderr.contains("Expected NullPointerException"));
}
diff --git a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
index 4f617ac..c96cbda 100644
--- a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.regress.b152973695;
-import static org.junit.Assert.assertTrue;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.fail;
import com.android.tools.r8.ClassFileConsumer;
@@ -12,6 +14,7 @@
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import java.io.IOException;
@@ -48,12 +51,13 @@
? new ClassFileConsumer.ArchiveConsumer(INVALID_FILE)
: new ArchiveConsumer(INVALID_FILE);
try {
- testForD8().addProgramClasses(Main.class).setProgramConsumer(programConsumer).compile();
+ testForD8()
+ .addProgramClasses(Main.class)
+ .setProgramConsumer(programConsumer)
+ .compileWithExpectedDiagnostics(diagnostics -> checkDiagnostics(diagnostics, true));
fail("Expected a CompilationFailedException but the code succeeded");
} catch (CompilationFailedException ex) {
- assertInvalidFileNotFound(ex);
- } catch (Throwable t) {
- fail("Expected a CompilationFailedException but got instead " + t);
+ // Expected.
}
}
@@ -69,18 +73,22 @@
.addProgramClasses(Main.class)
.addKeepMainRule(Main.class)
.setProgramConsumer(programConsumer)
- .compile();
+ .compileWithExpectedDiagnostics(diagnostics -> checkDiagnostics(diagnostics, false));
fail("Expected a CompilationFailedException but the code succeeded");
} catch (CompilationFailedException ex) {
- assertInvalidFileNotFound(ex);
- } catch (Throwable t) {
- fail("Expected a CompilationFailedException but got instead " + t);
+ // Expected.
}
}
- private void assertInvalidFileNotFound(CompilationFailedException ex) {
- assertTrue(ex.getCause().getMessage().contains("File not found"));
- assertTrue(ex.getCause().getMessage().contains(INVALID_FILE.toString()));
+ private void checkDiagnostics(TestDiagnosticMessages diagnostics, boolean isD8) {
+ if (classFileConsumer && isD8) {
+ diagnostics.assertWarningsMatch(
+ diagnosticMessage(
+ equalTo("Compiling to Java class files with D8 is not officially supported")));
+ } else {
+ diagnostics.assertOnlyErrors();
+ }
+ diagnostics.assertAllErrorsMatch(diagnosticMessage(containsString(INVALID_FILE.toString())));
}
private void ensureInvalidFileIsInvalid() {
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
index 62d8695..e891067 100644
--- a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
@@ -78,9 +77,9 @@
runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
// TODO(b/155618698): Extend relocator with a richer language such that java.lang.Object is not
// relocated.
- CompilationError compilationError =
+ RuntimeException compilationError =
assertThrows(
- CompilationError.class,
+ RuntimeException.class,
() ->
inspectAllClassesRelocated(
ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix + "."));
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
index 873788c..a0049f4 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -183,38 +183,27 @@
boolean enableJvmAssertions)
throws Exception {
- if (kotlinStdlibAsLibrary) {
- testForR8(parameters.getBackend())
- .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
- .addKeepMainRule(testClassKt)
- .addKeepClassAndMembersRules(class1, class2)
- .setMinApi(parameters.getApiLevel())
- .apply(builderConsumer)
- .noMinification()
- .addRunClasspathFiles(kotlinStdlibLibraryForRuntime())
- .compile()
- .enableRuntimeAssertions(enableJvmAssertions)
- .run(parameters.getRuntime(), testClassKt)
- .inspect(inspector)
- .assertSuccessWithOutputLines(outputLines);
- } else {
- testForR8(parameters.getBackend())
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
- .addKeepMainRule(testClassKt)
- .addKeepClassAndMembersRules(class1, class2)
- .setMinApi(parameters.getApiLevel())
- .apply(builderConsumer)
- .noMinification()
- .allowDiagnosticWarningMessages()
- .compile()
- .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
- .enableRuntimeAssertions(enableJvmAssertions)
- .run(parameters.getRuntime(), testClassKt)
- .inspect(inspector)
- .assertSuccessWithOutputLines(outputLines);
- }
+ testForR8(parameters.getBackend())
+ .applyIf(
+ kotlinStdlibAsLibrary,
+ b -> {
+ b.addClasspathFiles(ToolHelper.getKotlinStdlibJar());
+ b.addRunClasspathFiles(kotlinStdlibLibraryForRuntime());
+ },
+ b -> b.addProgramFiles(ToolHelper.getKotlinStdlibJar()))
+ .addProgramFiles(kotlinClasses.get(kotlinCompilationKey))
+ .addKeepMainRule(testClassKt)
+ .addKeepClassAndMembersRules(class1, class2)
+ .setMinApi(parameters.getApiLevel())
+ .apply(builderConsumer)
+ .allowDiagnosticWarningMessages(!kotlinStdlibAsLibrary)
+ .addRunClasspathFiles(kotlinStdlibLibraryForRuntime())
+ .compile()
+ .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .enableRuntimeAssertions(enableJvmAssertions)
+ .run(parameters.getRuntime(), testClassKt)
+ .inspect(inspector)
+ .assertSuccessWithOutputLines(outputLines);
}
private List<String> allAssertionsExpectedLines() {
@@ -484,7 +473,30 @@
}
@Test
- public void testAssertionsForCf() throws Exception {
+ public void testAssertionsForCfEnableWithStackMap() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
+ Assume.assumeTrue(useJvmAssertions);
+ Assume.assumeTrue(kotlinCompilationKey.targetVersion == KotlinTargetVersion.JAVA_8);
+ // Compile time enabling or disabling assertions means the -ea flag has no effect.
+ runR8Test(
+ builder -> {
+ builder.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions);
+ builder.addOptionsModification(options -> options.testing.readInputStackMaps = true);
+ },
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines());
+ runR8Test(
+ builder -> {
+ builder.addAssertionsConfiguration(AssertionsConfiguration.Builder::enableAllAssertions);
+ builder.addOptionsModification(options -> options.testing.readInputStackMaps = true);
+ },
+ inspector -> checkAssertionCodeEnabled(inspector, true),
+ allAssertionsExpectedLines(),
+ true);
+ }
+
+ @Test
+ public void testAssertionsForCfPassThrough() throws Exception {
Assume.assumeTrue(parameters.isCfRuntime());
// Leaving assertion code means assertions are controlled by the -ea flag.
runR8Test(
@@ -500,6 +512,11 @@
inspector -> checkAssertionCodeLeft(inspector, true),
allAssertionsExpectedLines(),
true);
+ }
+
+ @Test
+ public void testAssertionsForCfEnable() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
// Compile time enabling or disabling assertions means the -ea flag has no effect.
runR8Test(
builder ->
@@ -514,6 +531,11 @@
inspector -> checkAssertionCodeEnabled(inspector, true),
allAssertionsExpectedLines(),
true);
+ }
+
+ @Test
+ public void testAssertionsForCfDisable() throws Exception {
+ Assume.assumeTrue(parameters.isCfRuntime());
runR8Test(
builder ->
builder.addAssertionsConfiguration(
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index bbcc8e2..239697e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.rewrite.assertions;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -249,10 +250,9 @@
String main = ClassWithAssertions.class.getCanonicalName();
// When running on the JVM enable assertions. For Art this is not possible, and assertions
// can only be activated at compile time.
- assert parameters.getRuntime().isCf();
+ assertTrue(parameters.isCfRuntime());
result.enableRuntimeAssertions();
result
- .disassemble()
.run(parameters.getRuntime(), main, "0")
.assertFailureWithOutput(StringUtils.lines("1"));
// Assertion is not hit.
@@ -304,10 +304,10 @@
assertTrue(clazz.isPresent());
MethodSubject conditionMethod =
clazz.method(new MethodSignature("condition", "boolean", new String[]{}));
- assertTrue(!conditionMethod.isPresent());
+ assertFalse(conditionMethod.isPresent());
MethodSubject clinit =
clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
- assertTrue(!clinit.isPresent());
+ assertFalse(clinit.isPresent());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 5b119ce..2e123d7 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -22,12 +22,14 @@
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
import org.hamcrest.Matcher;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -252,6 +254,14 @@
}
private void checkTestRunResult(TestRunResult<?> result, Compiler compiler) {
+ Assume.assumeFalse(
+ "Triage (b/144966342)",
+ parameters.getRuntime().isDex()
+ && parameters
+ .getRuntime()
+ .asDex()
+ .getMinApiLevel()
+ .isGreaterThanOrEqualTo(AndroidApiLevel.Q));
switch (mode) {
case NO_INVOKE:
result.assertSuccessWithOutput(getExpectedOutput(compiler));
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index a06dea1..3b39b815 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -78,6 +78,8 @@
VALID_PROGUARD_DIR + "assume-no-side-effects-with-return-value.flags";
private static final String ASSUME_VALUES_WITH_RETURN_VALUE =
VALID_PROGUARD_DIR + "assume-values-with-return-value.flags";
+ private static final String ADAPT_KOTLIN_METADATA =
+ VALID_PROGUARD_DIR + "adapt-kotlin-metadata.flags";
private static final String INCLUDING =
VALID_PROGUARD_DIR + "including.flags";
private static final String INVALID_INCLUDING_1 =
@@ -770,6 +772,15 @@
}
@Test
+ public void parseAdaptKotlinMetadata() {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ Path path = Paths.get(ADAPT_KOTLIN_METADATA);
+ parser.parse(path);
+ checkDiagnostics(handler.infos, path, 1, 1, "Ignoring", "-adaptkotlinmetadata");
+ }
+
+ @Test
public void parseIncluding() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -831,14 +842,30 @@
for (String before : whiteSpace) {
for (String after : whiteSpace) {
reset();
- parseAndVerifyParserEndsCleanly(ImmutableList.of(
- "-keep"
- + before + "," + after + "includedescriptorclasses"
- + before + "," + after + "allowshrinking"
- + before + "," + after + "allowobfuscation"
- + before + "," + after + "allowoptimization "
- + "class A { *; }"
- ));
+ parseAndVerifyParserEndsCleanly(
+ ImmutableList.of(
+ "-keep"
+ + before
+ + ","
+ + after
+ + "includedescriptorclasses"
+ + before
+ + ","
+ + after
+ + "allowaccessmodification"
+ + before
+ + ","
+ + after
+ + "allowshrinking"
+ + before
+ + ","
+ + after
+ + "allowobfuscation"
+ + before
+ + ","
+ + after
+ + "allowoptimization "
+ + "class A { *; }"));
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
index cc02426..4c25b86 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -53,7 +53,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -77,20 +77,23 @@
.addProgramClasses(
B112517039ReturnType.class, B112517039I.class, B112517039Caller.class, MAIN)
.addKeepMainRule(MAIN)
- .setMinApi(parameters.getRuntime())
- .addOptionsModification(o -> {
- // No actual implementation of B112517039I, rather invoked with `null`.
- // Call site optimization propagation will conclude that the input of B...Caller#call is
- // always null, and replace the last call with null-throwing instruction.
- // However, we want to test return type and parameter type are kept in this scenario.
- o.enablePropagationOfDynamicTypesAtCallSites = false;
- o.enableInlining = false;
- })
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ o -> {
+ // No actual implementation of B112517039I, rather invoked with `null`.
+ // Call site optimization propagation will conclude that the input of B...Caller#call
+ // is
+ // always null, and replace the last call with null-throwing instruction.
+ // However, we want to test return type and parameter type are kept in this scenario.
+ o.callSiteOptimizationOptions().disableTypePropagationForTesting();
+ o.enableInlining = false;
+ })
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT)
- .inspect(inspector -> {
- ClassSubject returnType = inspector.clazz(B112517039ReturnType.class);
- assertThat(returnType, isRenamed());
- });
+ .inspect(
+ inspector -> {
+ ClassSubject returnType = inspector.clazz(B112517039ReturnType.class);
+ assertThat(returnType, isRenamed());
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index 6d0d767..d4801ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -20,6 +20,7 @@
@Test
public void testPrivateMethodsInLambdaClass() throws CompilationFailedException {
+ // This test only tests if the dump can be compiled without errors.
testForR8(Backend.DEX)
.addProgramClasses(Main.class, Interface.class)
.addProgramClassFileData(EventPublisher$bDump.dump())
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 8ef02dc..efcc42f 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -64,7 +64,7 @@
protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
try {
return new ApplicationReader(input, options, Timing.empty()).read();
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 92e0e10..47a6182 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -103,6 +103,11 @@
}
@Override
+ public String getFinalBinaryName() {
+ return null;
+ }
+
+ @Override
public boolean isRenamed() {
throw new Unreachable("Cannot determine if an absent class has been renamed");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index a234c36..8fed691 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -168,6 +168,8 @@
public abstract String getFinalDescriptor();
+ public abstract String getFinalBinaryName();
+
public abstract boolean isMemberClass();
public abstract boolean isLocalClass();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 7c0683f..6923021 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -278,6 +278,11 @@
}
@Override
+ public String getFinalBinaryName() {
+ return DescriptorUtils.getBinaryNameFromDescriptor(getFinalDescriptor());
+ }
+
+ @Override
public boolean isRenamed() {
return naming != null && !getFinalDescriptor().equals(getOriginalDescriptor());
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index c9975a3..5f50367 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -9,8 +9,10 @@
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.Box;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmFlexibleTypeUpperBound;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.jvm.JvmExtensionsKt;
@@ -63,6 +65,10 @@
return new KmClassifierSubject(kmType.classifier);
}
+ public KmFlexibleTypeUpperBound getFlexibleUpperBound() {
+ return kmType.getFlexibleTypeUpperBound();
+ }
+
@Override
public boolean isPresent() {
return true;
@@ -94,7 +100,7 @@
return areEqual(this.kmType, other.kmType, false);
}
- public static boolean areEqual(KmType one, KmType other, boolean checkAbbreviatedType) {
+ public static boolean areEqual(KmType one, KmType other, boolean checkInnerTypeReferences) {
if (one == null && other == null) {
return true;
}
@@ -116,14 +122,31 @@
return false;
}
}
- if (checkAbbreviatedType
- && !areEqual(one.getAbbreviatedType(), other.getAbbreviatedType(), checkAbbreviatedType)) {
+ if (checkInnerTypeReferences
+ && !areEqual(
+ one.getAbbreviatedType(), other.getAbbreviatedType(), checkInnerTypeReferences)) {
return false;
}
- if (!areEqual(one.getOuterType(), other.getOuterType(), checkAbbreviatedType)) {
+ if (!areEqual(one.getOuterType(), other.getOuterType(), checkInnerTypeReferences)) {
return false;
}
- // TODO(b/152745540): Add equality for flexibleUpperBoundType.
+ if ((one.getFlexibleTypeUpperBound() == null) != (other.getFlexibleTypeUpperBound() == null)
+ && checkInnerTypeReferences) {
+ return false;
+ }
+ if (one.getFlexibleTypeUpperBound() != null && checkInnerTypeReferences) {
+ if (!Objects.equals(
+ one.getFlexibleTypeUpperBound().getTypeFlexibilityId(),
+ other.getFlexibleTypeUpperBound().getTypeFlexibilityId())) {
+ return false;
+ }
+ if (!areEqual(
+ one.getFlexibleTypeUpperBound().getType(),
+ other.getFlexibleTypeUpperBound().getType(),
+ checkInnerTypeReferences)) {
+ return false;
+ }
+ }
if (JvmExtensionsKt.isRaw(one) != JvmExtensionsKt.isRaw(other)) {
return false;
}
diff --git a/src/test/proguard/valid/adapt-kotlin-metadata.flags b/src/test/proguard/valid/adapt-kotlin-metadata.flags
new file mode 100644
index 0000000..c6c2667
--- /dev/null
+++ b/src/test/proguard/valid/adapt-kotlin-metadata.flags
@@ -0,0 +1 @@
+-adaptkotlinmetadata
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 5aa5f97..511ec8d 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -20,7 +20,7 @@
parser.add_argument(
'-d',
'--dump',
- help='Dump file to compile',
+ help='Dump file or directory to compile',
default=None)
parser.add_argument(
'--temp',
@@ -85,12 +85,26 @@
def program_jar(self):
return self.if_exists('program.jar')
+ def feature_jars(self):
+ feature_jars = []
+ i = 1
+ while True:
+ feature_jar = self.if_exists('feature-%s.jar' % i)
+ if feature_jar:
+ feature_jars.append(feature_jar)
+ i = i + 1
+ else:
+ return feature_jars
+
def library_jar(self):
return self.if_exists('library.jar')
def classpath_jar(self):
return self.if_exists('classpath.jar')
+ def build_properties_file(self):
+ return self.if_exists('build.properties')
+
def config_file(self):
return self.if_exists('proguard.config')
@@ -105,12 +119,27 @@
def read_dump(args, temp):
if args.dump is None:
- error("A dump file must be specified")
- dump_file = zipfile.ZipFile(args.dump, 'r')
+ error("A dump file or directory must be specified")
+ if os.path.isdir(args.dump):
+ return Dump(args.dump)
+ dump_file = zipfile.ZipFile(os.path.abspath(args.dump), 'r')
with utils.ChangedWorkingDirectory(temp):
dump_file.extractall()
return Dump(temp)
+def determine_build_properties(args, dump):
+ build_properties = {}
+ build_properties_file = dump.build_properties_file()
+ if build_properties_file:
+ with open(build_properties_file) as f:
+ build_properties_contents = f.readlines()
+ for line in build_properties_contents:
+ stripped = line.strip()
+ if stripped:
+ pair = stripped.split('=')
+ build_properties[pair[0]] = pair[1]
+ return build_properties
+
def determine_version(args, dump):
if args.version is None:
return dump.version()
@@ -126,6 +155,9 @@
def determine_output(args, temp):
return os.path.join(temp, 'out.jar')
+def determine_feature_output(feature_jar, temp):
+ return os.path.join(temp, os.path.basename(feature_jar)[:-4] + ".out.jar")
+
def download_distribution(args, version, temp):
if version == 'master':
return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
@@ -157,6 +189,7 @@
if not os.path.exists(temp):
os.makedirs(temp)
dump = read_dump(args, temp)
+ build_properties = determine_build_properties(args, dump)
version = determine_version(args, dump)
compiler = determine_compiler(args, dump)
out = determine_output(args, temp)
@@ -181,6 +214,9 @@
cmd.append('--compat')
cmd.append(dump.program_jar())
cmd.extend(['--output', out])
+ for feature_jar in dump.feature_jars():
+ cmd.extend(['--feature-jar', feature_jar,
+ determine_feature_output(feature_jar, temp)])
if dump.library_jar():
cmd.extend(['--lib', dump.library_jar()])
if dump.classpath_jar():
@@ -189,6 +225,8 @@
cmd.extend(['--pg-conf', dump.config_file()])
if compiler != 'd8':
cmd.extend(['--pg-map-output', '%s.map' % out])
+ if 'min-api' in build_properties:
+ cmd.extend(['--min-api', build_properties.get('min-api')])
cmd.extend(otherargs)
utils.PrintCmd(cmd)
try:
diff --git a/tools/run-jdwp-tests.py b/tools/run-jdwp-tests.py
index 2ef479d..f40f2ce 100755
--- a/tools/run-jdwp-tests.py
+++ b/tools/run-jdwp-tests.py
@@ -15,6 +15,7 @@
VERSIONS = [
'default',
+ '10.0.0',
'9.0.0',
'8.1.0',
'7.0.0',
@@ -102,7 +103,7 @@
flags.extend(['-Ximage:%s' % IMAGE])
if version != '5.1.1':
flags.extend(['-Xcompiler-option', '--debuggable'])
- if version == '9.0.0':
+ if version == '9.0.0' or version == '10.0.0':
flags.extend(['-XjdwpProvider:internal'])
return flags
diff --git a/tools/test.py b/tools/test.py
index d6e4983..95766ca 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -156,10 +156,6 @@
def Main():
(options, args) = ParseOptions()
- # See b/144966342
- if options.dex_vm == '10.0.0':
- print 'Running on 10.0.0 is temporarily disabled, see b/144966342'
- return 0
if utils.is_bot():
gradle.RunGradle(['--no-daemon', 'clean'])