Merge commit 'c5a346eb80e2bfb3b279773a64c96c8de7bd403a' into dev-release
diff --git a/.gitignore b/.gitignore
index b03bb7b..24eead1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,10 @@
third_party/android_jar/lib-v[0-9][0-9]
third_party/android_jar/lib-v[0-9][0-9].tar.gz
third_party/android_jar/lib.tar.gz
+third_party/android_jar/api-versions.tar.gz
+third_party/android_jar/api-versions
+third_party/android_jar/api-database.tar.gz
+third_party/android_jar/api-database
third_party/android_sdk
third_party/android_sdk.tar.gz
third_party/bazel
diff --git a/build.gradle b/build.gradle
index cc51ea6..f69b505 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,13 +11,6 @@
import tasks.DownloadDependency
import tasks.GetJarsFromConfiguration
import utils.Utils
-import org.gradle.api.tasks.testing.logging.TestExceptionFormat
-import org.gradle.api.tasks.testing.logging.TestLogEvent
-import org.gradle.api.tasks.testing.TestOutputEvent
-
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-import java.nio.file.StandardOpenOption
buildscript {
repositories {
@@ -255,6 +248,7 @@
implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+ implementation files('third_party/android_jar/api-database/api-database.jar')
jdk11TimeTestsCompile group: 'org.testng', name: 'testng', version: testngVersion
examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
testCompile sourceSets.examples.output
@@ -315,6 +309,8 @@
"android_jar/lib-v29",
"android_jar/lib-v30",
"android_jar/lib-v31",
+ "android_jar/api-versions",
+ "android_jar/api-database",
"api-outlining/simple-app-dump",
"core-lambda-stubs",
"dart-sdk",
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 1a78e63..e02ccfb 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -408,7 +408,7 @@
AnnotationRemover annotationRemover =
annotationRemoverBuilder
.build(appViewWithLiveness, removedClasses);
- annotationRemover.ensureValid().run();
+ annotationRemover.ensureValid().run(executorService);
new GenericSignatureRewriter(appView, NamingLens.getIdentityLens(), genericContextBuilder)
.run(appView.appInfo().classes(), executorService);
}
@@ -642,7 +642,7 @@
// Remove annotations that refer to types that no longer exist.
AnnotationRemover.builder()
.build(appView.withLiveness(), removedClasses)
- .run();
+ .run(executorService);
new GenericSignatureRewriter(
appView, NamingLens.getIdentityLens(), genericContextBuilder)
.run(appView.appInfo().classes(), executorService);
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
new file mode 100644
index 0000000..f33f5bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2021, 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.androidapi;
+
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** This is a base class for all generated classes from api-versions.xml. */
+public abstract class AndroidApiClass {
+
+ private final ClassReference classReference;
+
+ public AndroidApiClass(ClassReference classReference) {
+ this.classReference = classReference;
+ }
+
+ public abstract AndroidApiLevel getApiLevel();
+
+ public abstract TraversalContinuation visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor);
+
+ public abstract TraversalContinuation visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor);
+
+ protected TraversalContinuation visitField(
+ String name,
+ String typeDescriptor,
+ int apiLevel,
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ return visitor.apply(
+ Reference.field(classReference, name, Reference.typeFromDescriptor(typeDescriptor)),
+ AndroidApiLevel.getAndroidApiLevel(apiLevel));
+ }
+
+ protected TraversalContinuation visitMethod(
+ String name,
+ String[] formalTypeDescriptors,
+ String returnType,
+ int apiLevel,
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ List<TypeReference> typeReferenceList = new ArrayList<>(formalTypeDescriptors.length);
+ for (String formalTypeDescriptor : formalTypeDescriptors) {
+ typeReferenceList.add(Reference.typeFromDescriptor(formalTypeDescriptor));
+ }
+ return visitor.apply(
+ Reference.method(
+ classReference,
+ name,
+ typeReferenceList,
+ returnType == null ? null : Reference.returnTypeFromDescriptor(returnType)),
+ AndroidApiLevel.getAndroidApiLevel(apiLevel));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 5ed4009..cfad75e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -101,6 +101,10 @@
// Pop the top value and push it back on with the checked type.
state.pop();
Slot object = state.push(type);
+ addCheckCast(builder, object);
+ }
+
+ void addCheckCast(IRBuilder builder, Slot object) {
builder.addCheckCast(object.register, type);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
new file mode 100644
index 0000000..e1473d3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSafeCheckCast.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2021, 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.cf.code;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfState.Slot;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import java.util.ListIterator;
+
+public class CfSafeCheckCast extends CfCheckCast {
+
+ public CfSafeCheckCast(DexType type) {
+ super(type);
+ }
+
+ @Override
+ void addCheckCast(IRBuilder builder, Slot object) {
+ builder.addSafeCheckCast(object.register, getType());
+ }
+
+ @Override
+ void internalRegisterUse(
+ UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+ registry.registerSafeCheckCast(getType());
+ }
+
+ @Override
+ public CfInstruction withType(DexType newType) {
+ return new CfSafeCheckCast(newType);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/code/SafeCheckCast.java b/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
new file mode 100644
index 0000000..e4af2a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/code/SafeCheckCast.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2021, 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.code;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+
+public class SafeCheckCast extends CheckCast {
+
+ SafeCheckCast(int high, BytecodeStream stream, OffsetToObjectMapping mapping) {
+ super(high, stream, mapping);
+ }
+
+ public SafeCheckCast(int valueRegister, DexType type) {
+ super(valueRegister, type);
+ }
+
+ @Override
+ public void buildIR(IRBuilder builder) {
+ builder.addSafeCheckCast(AA, getType());
+ }
+
+ @Override
+ public void registerUse(UseRegistry registry) {
+ registry.registerSafeCheckCast(getType());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index c915fe7..4770922 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -81,10 +81,17 @@
.allMatch(m -> m.tool == Tool.L8 || m.getDesugaredLibraryIdentifiers().length == 0);
} else {
String[] identifiers = marker.getDesugaredLibraryIdentifiers();
- String identifier;
+ String identifier = null;
switch (identifiers.length) {
case 0:
- identifier = NO_LIBRARY_DESUGARING;
+ // Only add the <no-library-desugaring> identifier for DEX. A marker from CF is
+ // assumed to go though desugaring for compiling to DEX, and that will introduce the
+ // DEX marker with the final library desugaring identifier.
+ if (marker.isDexBackend()) {
+ identifier = NO_LIBRARY_DESUGARING;
+ } else {
+ assert marker.isCfBackend();
+ }
break;
case 1:
identifier = identifiers[0];
@@ -95,16 +102,16 @@
new StringDiagnostic(
"Merging program compiled with multiple desugared libraries."));
}
- if (marker.isDesugared()) {
+ if (marker.isDesugared() && identifier != null) {
desugaredLibraryIdentifiers.add(identifier);
} else {
- assert identifier.equals(NO_LIBRARY_DESUGARING);
+ assert identifier == null || identifier.equals(NO_LIBRARY_DESUGARING);
}
}
}
if (desugaredLibraryIdentifiers.size() > 1) {
- reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers));
+ reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers, markers));
}
}
@@ -211,11 +218,19 @@
public String getBackend() {
if (!hasBackend()) {
// Before adding backend we would always compile to dex if min-api was specified.
- return hasMinApi() ? "dex" : "cf";
+ return hasMinApi() ? Backend.DEX.name().toLowerCase() : Backend.CF.name().toLowerCase();
}
return jsonObject.get(BACKEND).getAsString();
}
+ public boolean isCfBackend() {
+ return getBackend().equals(Backend.CF.name().toLowerCase());
+ }
+
+ public boolean isDexBackend() {
+ return getBackend().equals(Backend.DEX.name().toLowerCase());
+ }
+
public Marker setBackend(Backend backend) {
assert !hasBackend();
jsonObject.addProperty(BACKEND, backend.name().toLowerCase());
diff --git a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
index ba9af51..5141753 100644
--- a/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/DesugaredLibraryMismatchDiagnostic.java
@@ -5,16 +5,21 @@
package com.android.tools.r8.errors;
import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import java.util.Set;
+import java.util.stream.Collectors;
public class DesugaredLibraryMismatchDiagnostic implements Diagnostic {
private final Set<String> desugaredLibraryIdentifiers;
+ private final Set<Marker> markers;
- public DesugaredLibraryMismatchDiagnostic(Set<String> desugaredLibraryIdentifiers) {
+ public DesugaredLibraryMismatchDiagnostic(
+ Set<String> desugaredLibraryIdentifiers, Set<Marker> markers) {
this.desugaredLibraryIdentifiers = desugaredLibraryIdentifiers;
+ this.markers = markers;
}
@Override
@@ -31,6 +36,8 @@
public String getDiagnosticMessage() {
return "The compilation is merging inputs with different desugared library desugaring "
+ desugaredLibraryIdentifiers
- + ", which may lead to unexpected runtime errors.";
+ + ", which may lead to unexpected runtime errors."
+ + "The markers are "
+ + markers.stream().map(Marker::toString).collect(Collectors.joining(", "));
}
}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
index 5242510..7baa5fa 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
@@ -4,31 +4,44 @@
package com.android.tools.r8.experimental.graphinfo;
import com.android.tools.r8.Keep;
+import java.util.Objects;
@Keep
public final class AnnotationGraphNode extends GraphNode {
private final GraphNode annotatedNode;
+ private final ClassGraphNode annotationClassNode;
- public AnnotationGraphNode(GraphNode annotatedNode) {
+ public AnnotationGraphNode(GraphNode annotatedNode, ClassGraphNode annotationClassNode) {
super(annotatedNode.isLibraryNode());
this.annotatedNode = annotatedNode;
+ this.annotationClassNode = annotationClassNode;
}
public GraphNode getAnnotatedNode() {
return annotatedNode;
}
+ public ClassGraphNode getAnnotationClassNode() {
+ return annotationClassNode;
+ }
+
@Override
public boolean equals(Object o) {
- return this == o
- || (o instanceof AnnotationGraphNode
- && ((AnnotationGraphNode) o).annotatedNode.equals(annotatedNode));
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof AnnotationGraphNode)) {
+ return false;
+ }
+ AnnotationGraphNode node = (AnnotationGraphNode) o;
+ return annotatedNode.equals(node.annotatedNode)
+ && annotationClassNode.equals(node.annotationClassNode);
}
@Override
public int hashCode() {
- return 7 * annotatedNode.hashCode();
+ return Objects.hash(annotatedNode, annotationClassNode);
}
@Override
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 6ef4c6a..b509c1f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -323,6 +323,13 @@
return callSiteOptimizationInfoPropagator;
}
+ public <E extends Throwable> void withCallSiteOptimizationInfoPropagator(
+ ThrowingConsumer<CallSiteOptimizationInfoPropagator, E> consumer) throws E {
+ if (callSiteOptimizationInfoPropagator != null) {
+ consumer.accept(callSiteOptimizationInfoPropagator);
+ }
+ }
+
public LibraryMemberOptimizer libraryMethodOptimizer() {
return libraryMemberOptimizer;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 14f0015..3d1a246 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -151,6 +151,17 @@
private StackMapStatus stackMapStatus = StackMapStatus.NOT_VERIFIED;
public CfCode(
+ DexType originalHolder, int maxStack, int maxLocals, List<CfInstruction> instructions) {
+ this(
+ originalHolder,
+ maxStack,
+ maxLocals,
+ instructions,
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ public CfCode(
DexType originalHolder,
int maxStack,
int maxLocals,
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 0576326..1141602 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -25,6 +25,30 @@
import java.util.function.Function;
public class DexAnnotation extends DexItem implements StructuralItem<DexAnnotation> {
+
+ public enum AnnotatedKind {
+ FIELD,
+ METHOD,
+ TYPE,
+ PARAMETER;
+
+ public static AnnotatedKind from(DexDefinition definition) {
+ return from(definition.getReference());
+ }
+
+ public static AnnotatedKind from(ProgramDefinition definition) {
+ return from(definition.getReference());
+ }
+
+ public static AnnotatedKind from(DexReference reference) {
+ return reference.apply(type -> TYPE, field -> FIELD, method -> METHOD);
+ }
+
+ public boolean isParameter() {
+ return this == PARAMETER;
+ }
+ }
+
public static final DexAnnotation[] EMPTY_ARRAY = {};
public static final int VISIBILITY_BUILD = 0x00;
public static final int VISIBILITY_RUNTIME = 0x01;
@@ -300,6 +324,11 @@
return annotation.annotation.type == factory.annotationDefault;
}
+ public static boolean isJavaLangRetentionAnnotation(
+ DexAnnotation annotation, DexItemFactory factory) {
+ return annotation.getAnnotationType() == factory.retentionType;
+ }
+
public static boolean isSourceDebugExtension(DexAnnotation annotation,
DexItemFactory factory) {
return annotation.annotation.type == factory.annotationSourceDebugExtension;
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index e1a64b1..454be57 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -7,6 +7,9 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.structural.StructuralItem;
@@ -41,6 +44,18 @@
this.annotations = annotations;
}
+ public DexAnnotationSet create(DexAnnotation[] annotations) {
+ return ArrayUtils.isEmpty(annotations) ? empty() : new DexAnnotationSet(annotations);
+ }
+
+ public DexAnnotation get(int index) {
+ return annotations[index];
+ }
+
+ public DexAnnotation getFirst() {
+ return get(0);
+ }
+
@Override
public DexAnnotationSet self() {
return this;
@@ -189,14 +204,45 @@
if (isEmpty()) {
return this;
}
- DexAnnotation[] rewritten = ArrayUtils.map(DexAnnotation[].class, annotations, rewriter);
- if (rewritten == annotations) {
- return this;
+ DexAnnotation[] rewritten = ArrayUtils.map(annotations, rewriter, DexAnnotation.EMPTY_ARRAY);
+ return rewritten != annotations ? create(rewritten) : this;
+ }
+
+ public DexAnnotationSet methodParametersWithFakeThisArguments(DexItemFactory factory) {
+ DexAnnotation[] newAnnotations = null;
+ for (int i = 0; i < annotations.length; i++) {
+ DexAnnotation annotation = annotations[i];
+ if (annotation.annotation.type == factory.annotationMethodParameters) {
+ assert annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM;
+ assert annotation.annotation.elements.length == 2;
+ assert annotation.annotation.elements[0].name.toString().equals("names");
+ assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
+ DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
+ DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
+ assert names != null && accessFlags != null;
+ assert names.getValues().length == accessFlags.getValues().length;
+ if (newAnnotations == null) {
+ newAnnotations = new DexAnnotation[annotations.length];
+ System.arraycopy(annotations, 0, newAnnotations, 0, i);
+ }
+ DexValue[] newNames = new DexValue[names.getValues().length + 1];
+ newNames[0] =
+ new DexValueString(
+ factory.createString(DexCode.FAKE_THIS_PREFIX + DexCode.FAKE_THIS_SUFFIX));
+ System.arraycopy(names.getValues(), 0, newNames, 1, names.getValues().length);
+ DexValue[] newAccessFlags = new DexValue[accessFlags.getValues().length + 1];
+ newAccessFlags[0] = DexValueInt.create(0);
+ System.arraycopy(
+ accessFlags.getValues(), 0, newAccessFlags, 1, accessFlags.getValues().length);
+ newAnnotations[i] =
+ DexAnnotation.createMethodParametersAnnotation(newNames, newAccessFlags, factory);
+ } else {
+ if (newAnnotations != null) {
+ newAnnotations[i] = annotation;
+ }
+ }
}
- if (rewritten.length == 0) {
- return DexAnnotationSet.empty();
- }
- return new DexAnnotationSet(rewritten);
+ return newAnnotations == null ? this : new DexAnnotationSet(newAnnotations);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 182c91c..41dd2c1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -314,6 +314,15 @@
fields(predicate).forEach(consumer);
}
+ public void forEachInstanceField(Consumer<DexEncodedField> consumer) {
+ forEachInstanceFieldMatching(alwaysTrue(), consumer);
+ }
+
+ public void forEachInstanceFieldMatching(
+ Predicate<DexEncodedField> predicate, Consumer<DexEncodedField> consumer) {
+ instanceFields(predicate).forEach(consumer);
+ }
+
public TraversalContinuation traverseFields(Function<DexEncodedField, TraversalContinuation> fn) {
for (DexEncodedField field : fields()) {
if (fn.apply(field).shouldBreak()) {
@@ -393,6 +402,10 @@
return Arrays.asList(instanceFields);
}
+ public Iterable<DexEncodedField> instanceFields(Predicate<? super DexEncodedField> predicate) {
+ return Iterables.filter(Arrays.asList(instanceFields), predicate::test);
+ }
+
public void appendInstanceField(DexEncodedField field) {
DexEncodedField[] newFields = new DexEncodedField[instanceFields.length + 1];
System.arraycopy(instanceFields, 0, newFields, 0, instanceFields.length);
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e4f7973..378640d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -38,8 +38,8 @@
// DexCode corresponds to code item in dalvik/dex-format.html
public class DexCode extends Code implements StructuralItem<DexCode> {
- static final String FAKE_THIS_PREFIX = "_";
- static final String FAKE_THIS_SUFFIX = "this";
+ public static final String FAKE_THIS_PREFIX = "_";
+ public static final String FAKE_THIS_SUFFIX = "this";
public final int registerSize;
public final int incomingRegisterSize;
@@ -259,7 +259,7 @@
}
for (TryHandler handler : handlers) {
for (TypeAddrPair pair : handler.pairs) {
- registry.registerTypeReference(pair.type);
+ registry.registerExceptionGuard(pair.type);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinition.java b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
index 8eeda6b..2695b1f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinition.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.shaking.AnnotationRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -31,15 +31,14 @@
public abstract AccessFlags<?> getAccessFlags();
- public DexAnnotationSet liveAnnotations(AppView<AppInfoWithLiveness> appView) {
- return annotations.keepIf(
- annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
- }
-
public void clearAnnotations() {
setAnnotations(DexAnnotationSet.empty());
}
+ public void clearAllAnnotations() {
+ clearAnnotations();
+ }
+
public void setAnnotations(DexAnnotationSet annotations) {
this.annotations = annotations;
}
@@ -48,6 +47,12 @@
setAnnotations(annotations().removeIf(predicate));
}
+ public void rewriteAllAnnotations(
+ BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+ setAnnotations(
+ annotations().rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.from(this))));
+ }
+
public boolean isDexClass() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index 98df8c7..e338ef5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -112,7 +112,7 @@
Function<DexAnnotationElement, DexAnnotationElement> elementRewriter) {
DexType rewrittenType = typeRewriter.apply(type);
DexAnnotationElement[] rewrittenElements =
- ArrayUtils.map(DexAnnotationElement[].class, elements, elementRewriter);
+ ArrayUtils.map(elements, elementRewriter, DexAnnotationElement.EMPTY_ARRAY);
if (rewrittenType == type && rewrittenElements == elements) {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 1e10fa9..da8323c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationWithMinApiInfo;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -30,6 +31,10 @@
public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
implements StructuralItem<DexEncodedField> {
+
+ public static final boolean D8_R8_SYNTHESIZED = true;
+ public static final boolean NOT_DEPRECATED = false;
+ public static final DexValue NO_STATIC_VALUE = null;
public static final DexEncodedField[] EMPTY_ARRAY = {};
public final FieldAccessFlags accessFlags;
@@ -132,6 +137,10 @@
optimizationInfo = info;
}
+ public void setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo info) {
+ optimizationInfo = info;
+ }
+
@Override
public KotlinFieldLevelInfo getKotlinInfo() {
return kotlinMemberInfo;
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 449747b..f8a2537 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -44,6 +44,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.code.IRCode;
@@ -72,8 +73,6 @@
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.shaking.AnnotationRemover;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -92,6 +91,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntPredicate;
@@ -399,11 +399,6 @@
return getReference().proto.returnType;
}
- public ParameterAnnotationsList liveParameterAnnotations(AppView<AppInfoWithLiveness> appView) {
- return parameterAnnotationsList.keepIf(
- annotation -> AnnotationRemover.shouldKeepAnnotation(appView, this, annotation));
- }
-
public OptionalBool isLibraryMethodOverride() {
return isNonPrivateVirtualMethod() ? isLibraryMethodOverride : OptionalBool.FALSE;
}
@@ -894,14 +889,38 @@
return builder.toString();
}
- public ParameterAnnotationsList getParameterAnnotations() {
- return parameterAnnotationsList;
+ @Override
+ public void clearAllAnnotations() {
+ clearAnnotations();
+ clearParameterAnnotations();
+ }
+
+ @Override
+ public void rewriteAllAnnotations(
+ BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+ setAnnotations(
+ annotations().rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.METHOD)));
+ setParameterAnnotations(
+ getParameterAnnotations()
+ .rewrite(annotation -> rewriter.apply(annotation, AnnotatedKind.PARAMETER)));
}
public void clearParameterAnnotations() {
parameterAnnotationsList = ParameterAnnotationsList.empty();
}
+ public DexAnnotationSet getParameterAnnotation(int index) {
+ return getParameterAnnotations().get(index);
+ }
+
+ public ParameterAnnotationsList getParameterAnnotations() {
+ return parameterAnnotationsList;
+ }
+
+ public void setParameterAnnotations(ParameterAnnotationsList parameterAnnotations) {
+ this.parameterAnnotationsList = parameterAnnotations;
+ }
+
public String toSmaliString(ClassNameMapper naming) {
checkIfObsolete();
StringBuilder builder = new StringBuilder();
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 a42388a..d7a48c1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -450,6 +451,8 @@
createStaticallyKnownType("Ljava/util/function/LongConsumer;");
public final DexType intConsumer = createStaticallyKnownType("Ljava/util/function/IntConsumer;");
+ public final DexType retentionType =
+ createStaticallyKnownType("Ljava/lang/annotation/Retention;");
public final DexType runtimeExceptionType = createStaticallyKnownType(runtimeExceptionDescriptor);
public final DexType throwableType = createStaticallyKnownType(throwableDescriptor);
public final DexType illegalAccessErrorType =
@@ -1508,16 +1511,16 @@
}
public class JavaLangSystemMethods {
- public final DexMethod identityHashCode;
- private JavaLangSystemMethods() {
- identityHashCode =
- createMethod(
- javaLangSystemDescriptor,
- identityHashCodeName,
- intDescriptor,
- new DexString[] {objectDescriptor});
- }
+ public final DexMethod arraycopy =
+ createMethod(
+ javaLangSystemType,
+ createProto(voidType, objectType, intType, objectType, intType, intType),
+ "arraycopy");
+ public final DexMethod identityHashCode =
+ createMethod(javaLangSystemType, createProto(intType, objectType), identityHashCodeName);
+
+ private JavaLangSystemMethods() {}
}
public class EnumMembers {
@@ -2242,6 +2245,10 @@
return createMethod(holder, createProto(voidType, parameters), constructorMethodName);
}
+ public DexMethod createInstanceInitializer(DexType holder, DexTypeList parameters) {
+ return createInstanceInitializer(holder, parameters.values);
+ }
+
public DexMethod createInstanceInitializerWithFreshProto(
DexMethod method, List<DexType> extraTypes, Predicate<DexMethod> isFresh) {
assert method.isInstanceInitializer(this);
@@ -2400,6 +2407,13 @@
return createField(clazz, type, createString(name));
}
+ public DexField createField(FieldReference fieldReference) {
+ return createField(
+ createType(fieldReference.getHolderClass().getDescriptor()),
+ createType(fieldReference.getFieldType().getDescriptor()),
+ fieldReference.getFieldName());
+ }
+
public DexProto createProto(DexType returnType, DexTypeList parameters, DexString shorty) {
assert !sorted;
DexProto proto = new DexProto(shorty, returnType, parameters);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 9b62855..ae9d9fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.Iterables;
+import java.util.Map;
import java.util.function.Function;
public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
@@ -60,4 +62,14 @@
public Iterable<DexType> getReferencedBaseTypes(DexItemFactory dexItemFactory) {
return Iterables.transform(getReferencedTypes(), type -> type.toBaseType(dexItemFactory));
}
+
+ public AndroidApiLevel computeApiLevelForReferencedTypes(
+ AppView<?> appView, Map<DexReference, AndroidApiLevel> apiLevelMap) {
+ AndroidApiLevel minApiLevel = appView.options().minApiLevel;
+ AndroidApiLevel apiLevel = minApiLevel;
+ for (DexType type : getReferencedBaseTypes(appView.dexItemFactory())) {
+ apiLevel = apiLevel.max(apiLevelMap.getOrDefault(type, minApiLevel));
+ }
+ return apiLevel;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 6be5551..0ebd1da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -199,6 +199,10 @@
forEachField(field -> consumer.accept(new ProgramField(this, field)));
}
+ public void forEachProgramInstanceField(Consumer<? super ProgramField> consumer) {
+ forEachInstanceField(field -> consumer.accept(new ProgramField(this, field)));
+ }
+
public void forEachProgramMember(Consumer<? super ProgramMember<?, ?>> consumer) {
forEachProgramField(consumer);
forEachProgramMethod(consumer);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 1697f43..850c3cb 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -58,6 +58,14 @@
return this;
}
+ public static FieldAccessFlags createPublicStaticFinalSynthetic() {
+ return fromSharedAccessFlags(
+ Constants.ACC_PUBLIC
+ | Constants.ACC_STATIC
+ | Constants.ACC_FINAL
+ | Constants.ACC_SYNTHETIC);
+ }
+
public static FieldAccessFlags createPublicStaticSynthetic() {
return fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
diff --git a/src/main/java/com/android/tools/r8/graph/InvalidCode.java b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
new file mode 100644
index 0000000..418de86
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/InvalidCode.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2021, 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.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+
+public class InvalidCode extends Code {
+
+ private static final InvalidCode INSTANCE = new InvalidCode();
+
+ public static Code getInstance() {
+ return INSTANCE;
+ }
+
+ public static boolean isInvalidCode(Code code) {
+ return code == INSTANCE;
+ }
+
+ private InvalidCode() {}
+
+ @Override
+ public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public String toString() {
+ return "<invalid-code>";
+ }
+
+ @Override
+ public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+ return toString();
+ }
+
+ @Override
+ public int estimatedDexCodeSizeUpperBoundInBytes() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isEmptyVoidMethod() {
+ throw new Unreachable();
+ }
+
+ @Override
+ protected int computeHashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ protected boolean computeEquals(Object other) {
+ return this == other;
+ }
+}
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 17072ef..7e67c3b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.PredicateUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.base.MoreObjects;
@@ -265,15 +266,13 @@
@Override
void addVirtualMethod(DexEncodedMethod virtualMethod) {
assert belongsToVirtualPool(virtualMethod);
- virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
- virtualMethods[virtualMethods.length - 1] = virtualMethod;
+ virtualMethods = ArrayUtils.appendSingleElement(virtualMethods, virtualMethod);
}
@Override
void addDirectMethod(DexEncodedMethod directMethod) {
assert belongsToDirectPool(directMethod);
- directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
- directMethods[directMethods.length - 1] = directMethod;
+ directMethods = ArrayUtils.appendSingleElement(directMethods, directMethod);
}
@Override
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 35e0541..635132f 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -137,7 +138,7 @@
public List<DexEncodedMethod> allMethodsSorted() {
List<DexEncodedMethod> sorted = new ArrayList<>(size());
forEachMethod(sorted::add);
- sorted.sort((a, b) -> a.getReference().compareTo(b.getReference()));
+ sorted.sort(Comparator.comparing(DexEncodedMember::getReference));
return sorted;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
index 7628702..ba98190 100644
--- a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -70,6 +70,13 @@
this.missingParameterAnnotations = missingParameterAnnotations;
}
+ public static ParameterAnnotationsList create(
+ DexAnnotationSet[] values, int missingParameterAnnotations) {
+ return ArrayUtils.isEmpty(values)
+ ? empty()
+ : new ParameterAnnotationsList(values, missingParameterAnnotations);
+ }
+
@Override
public ParameterAnnotationsList self() {
return this;
@@ -187,6 +194,17 @@
return new ParameterAnnotationsList(values, parameterCount - values.length);
}
+ public ParameterAnnotationsList withFakeThisParameter() {
+ // If there are no parameter annotations there is no need to add one for the this parameter.
+ if (isEmpty()) {
+ return this;
+ }
+ DexAnnotationSet[] newValues = new DexAnnotationSet[size() + 1];
+ System.arraycopy(values, 0, newValues, 1, size());
+ newValues[0] = DexAnnotationSet.empty();
+ return new ParameterAnnotationsList(newValues, 0);
+ }
+
/**
* Return a new ParameterAnnotationsList that keeps only the annotations matched by {@code
* filter}.
@@ -215,11 +233,15 @@
return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
}
- public ParameterAnnotationsList rewrite(Function<DexAnnotationSet, DexAnnotationSet> mapper) {
- DexAnnotationSet[] rewritten = ArrayUtils.map(DexAnnotationSet[].class, values, mapper);
- if (rewritten != values) {
- return new ParameterAnnotationsList(rewritten, missingParameterAnnotations);
+ public ParameterAnnotationsList rewrite(Function<DexAnnotation, DexAnnotation> mapper) {
+ if (isEmpty()) {
+ return this;
}
- return this;
+ DexAnnotationSet[] rewritten =
+ ArrayUtils.map(
+ values, annotations -> annotations.rewrite(mapper), DexAnnotationSet.EMPTY_ARRAY);
+ return rewritten != values
+ ? ParameterAnnotationsList.create(rewritten, missingParameterAnnotations)
+ : this;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
index 17e779e..c1dedd5 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDefinition.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.graph;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
+import java.util.function.BiFunction;
import java.util.function.Function;
public interface ProgramDefinition extends Definition, ProgramDerivedContext {
@@ -21,6 +23,15 @@
return this;
}
+ default void clearAllAnnotations() {
+ getDefinition().clearAllAnnotations();
+ }
+
+ default void rewriteAllAnnotations(
+ BiFunction<DexAnnotation, AnnotatedKind, DexAnnotation> rewriter) {
+ getDefinition().rewriteAllAnnotations(rewriter);
+ }
+
DexProgramClass getContextClass();
AccessFlags<?> getAccessFlags();
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 3e2a276..aec6be0 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -5,7 +5,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
public class ProgramField extends DexClassAndField
implements ProgramMember<DexEncodedField, DexField> {
@@ -60,12 +60,7 @@
}
@Override
- public KotlinMemberLevelInfo getKotlinInfo() {
+ public KotlinFieldLevelInfo getKotlinInfo() {
return getDefinition().getKotlinInfo();
}
-
- @Override
- public void clearKotlinInfo() {
- getDefinition().clearKotlinInfo();
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMember.java b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
index 1234378..fa77682 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMember.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMember.java
@@ -23,5 +23,11 @@
KotlinMemberLevelInfo getKotlinInfo();
- void clearKotlinInfo();
+ default void clearGenericSignature() {
+ getDefinition().clearGenericSignature();
+ }
+
+ default void clearKotlinInfo() {
+ getDefinition().clearKotlinInfo();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 5a58189..5faf268 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
+import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
@@ -95,15 +95,10 @@
}
@Override
- public KotlinMemberLevelInfo getKotlinInfo() {
+ public KotlinMethodLevelInfo getKotlinInfo() {
return getDefinition().getKotlinInfo();
}
- @Override
- public void clearKotlinInfo() {
- getDefinition().clearKotlinInfo();
- }
-
public MethodPosition getPosition() {
return getDefinition().getPosition();
}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 565511a..708a02f 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -82,6 +82,10 @@
registerTypeReference(type);
}
+ public void registerSafeCheckCast(DexType type) {
+ registerCheckCast(type);
+ }
+
public void registerExceptionGuard(DexType guard) {
registerTypeReference(guard);
}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
new file mode 100644
index 0000000..f93437e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2021, 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.graph.analysis;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationWithMinApiInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
+import com.android.tools.r8.ir.optimize.info.MemberOptimizationInfo;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Map;
+
+public class ApiModelAnalysis extends EnqueuerAnalysis {
+
+ private final AppView<?> appView;
+ private final AndroidApiLevel minApiLevel;
+ private final Map<DexReference, AndroidApiLevel> referenceToApiLevelMap;
+
+ public ApiModelAnalysis(
+ AppView<?> appView, Map<DexReference, AndroidApiLevel> referenceToApiLevelMap) {
+ this.appView = appView;
+ this.minApiLevel = appView.options().minApiLevel;
+ this.referenceToApiLevelMap = referenceToApiLevelMap;
+ }
+
+ @Override
+ public void processNewlyLiveField(ProgramField field, ProgramDefinition context) {
+ setApiLevelForMember(
+ field.getDefinition(), computeApiLevelForReferencedTypes(field.getReference()));
+ }
+
+ @Override
+ public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
+ setApiLevelForMember(
+ method.getDefinition(), computeApiLevelForReferencedTypes(method.getReference()));
+ }
+
+ @Override
+ public void processTracedCode(ProgramMethod method, DefaultEnqueuerUseRegistry registry) {
+ assert registry.getMaxApiReferenceLevel().isGreaterThanOrEqualTo(minApiLevel);
+ setApiLevelForMember(method.getDefinition(), registry.getMaxApiReferenceLevel());
+ }
+
+ private void setApiLevelForMember(DexEncodedMember<?, ?> member, AndroidApiLevel apiLevel) {
+ // To not have mutable update information for all members that all has min api level we
+ // swap the default optimization info for one with that marks the api level to be min api.
+ MemberOptimizationInfo<?> optimizationInfo = member.getOptimizationInfo();
+ if (!optimizationInfo.isMutableOptimizationInfo() && apiLevel == minApiLevel) {
+ member.apply(
+ field -> {
+ field.setMinApiOptimizationInfo(DefaultFieldOptimizationWithMinApiInfo.getInstance());
+ },
+ method -> {
+ method.setMinApiOptimizationInfo(DefaultMethodOptimizationWithMinApiInfo.getInstance());
+ });
+ } else {
+ AndroidApiLevel maxApiLevel =
+ optimizationInfo.hasApiReferenceLevel()
+ ? apiLevel.max(optimizationInfo.getApiReferenceLevel(minApiLevel))
+ : apiLevel;
+ member.apply(
+ field -> {
+ field.getMutableOptimizationInfo().setApiReferenceLevel(maxApiLevel);
+ },
+ method -> {
+ method.getMutableOptimizationInfo().setApiReferenceLevel(maxApiLevel);
+ });
+ }
+ }
+
+ private AndroidApiLevel computeApiLevelForReferencedTypes(DexMember<?, ?> member) {
+ return member.computeApiLevelForReferencedTypes(appView, referenceToApiLevelMap);
+ }
+}
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 9e09724..dc87519 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
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerWorklist;
import com.android.tools.r8.utils.Timing;
@@ -26,6 +27,9 @@
/** Called when a method is found to be live. */
public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {}
+ /** Called when a method's code has been processed by the registry. */
+ public void processTracedCode(ProgramMethod method, DefaultEnqueuerUseRegistry registry) {}
+
/**
* Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
* analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
index 503e74e..ad0dcbf 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
@@ -8,5 +8,8 @@
import com.android.tools.r8.graph.ProgramMethod;
public interface EnqueuerCheckCastAnalysis {
+
void traceCheckCast(DexType type, ProgramMethod context);
+
+ void traceSafeCheckCast(DexType type, ProgramMethod context);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index e0eaf2c..21f8a5c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -8,12 +8,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder;
import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
-import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
@@ -22,6 +19,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
public class ClassInstanceFieldsMerger {
@@ -31,10 +29,6 @@
private DexEncodedField classIdField;
- // Map from target class field to all fields which should be merged into that field.
- private final MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> fieldMappings =
- BidirectionalManyToOneHashMap.newLinkedHashMap();
-
public ClassInstanceFieldsMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
HorizontalClassMergerGraphLens.Builder lensBuilder,
@@ -42,7 +36,6 @@
this.appView = appView;
this.group = group;
this.lensBuilder = lensBuilder;
- group.forEachSource(this::addFields);
}
/**
@@ -55,13 +48,17 @@
* Bar has fields 'A b' and 'B a'), we make a prepass that matches fields with the same reference
* type.
*/
- private void addFields(DexProgramClass clazz) {
+ public static void mapFields(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ DexProgramClass source,
+ DexProgramClass target,
+ BiConsumer<DexEncodedField, DexEncodedField> consumer) {
Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo =
- getAvailableFieldsByExactInfo();
+ getAvailableFieldsByExactInfo(target);
List<DexEncodedField> needsMerge = new ArrayList<>();
// Pass 1: Match fields that have the exact same type.
- for (DexEncodedField oldField : clazz.instanceFields()) {
+ for (DexEncodedField oldField : source.instanceFields()) {
InstanceFieldInfo info = InstanceFieldInfo.createExact(oldField);
LinkedList<DexEncodedField> availableFieldsWithExactSameInfo =
availableFieldsByExactInfo.get(info);
@@ -69,7 +66,7 @@
needsMerge.add(oldField);
} else {
DexEncodedField newField = availableFieldsWithExactSameInfo.removeFirst();
- fieldMappings.put(oldField, newField);
+ consumer.accept(oldField, newField);
if (availableFieldsWithExactSameInfo.isEmpty()) {
availableFieldsByExactInfo.remove(info);
}
@@ -78,7 +75,7 @@
// Pass 2: Match fields that do not have the same reference type.
Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
- getAvailableFieldsByRelaxedInfo(availableFieldsByExactInfo);
+ getAvailableFieldsByRelaxedInfo(appView, availableFieldsByExactInfo);
for (DexEncodedField oldField : needsMerge) {
assert oldField.getType().isReferenceType();
DexEncodedField newField =
@@ -87,14 +84,15 @@
.removeFirst();
assert newField != null;
assert newField.getType().isReferenceType();
- fieldMappings.put(oldField, newField);
+ consumer.accept(oldField, newField);
}
}
- private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo() {
+ private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByExactInfo(
+ DexProgramClass target) {
Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByInfo =
new LinkedHashMap<>();
- for (DexEncodedField field : group.getTarget().instanceFields()) {
+ for (DexEncodedField field : target.instanceFields()) {
availableFieldsByInfo
.computeIfAbsent(InstanceFieldInfo.createExact(field), ignore -> new LinkedList<>())
.add(field);
@@ -102,8 +100,10 @@
return availableFieldsByInfo;
}
- private Map<InstanceFieldInfo, LinkedList<DexEncodedField>> getAvailableFieldsByRelaxedInfo(
- Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
+ private static Map<InstanceFieldInfo, LinkedList<DexEncodedField>>
+ getAvailableFieldsByRelaxedInfo(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByExactInfo) {
Map<InstanceFieldInfo, LinkedList<DexEncodedField>> availableFieldsByRelaxedInfo =
new LinkedHashMap<>();
availableFieldsByExactInfo.forEach(
@@ -125,26 +125,21 @@
}
}
- public ProgramField getTargetField(ProgramField field) {
- if (field.getHolder() == group.getTarget()) {
- return field;
- }
- DexEncodedField targetField = fieldMappings.get(field.getDefinition());
- return new ProgramField(group.getTarget(), targetField);
- }
-
public void setClassIdField(DexEncodedField classIdField) {
this.classIdField = classIdField;
}
public DexEncodedField[] merge() {
+ assert group.hasInstanceFieldMap();
List<DexEncodedField> newFields = new ArrayList<>();
if (classIdField != null) {
newFields.add(classIdField);
}
- fieldMappings.forEachManyToOneMapping(
- (sourceFields, targetField) ->
- newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
+ group
+ .getInstanceFieldMap()
+ .forEachManyToOneMapping(
+ (sourceFields, targetField) ->
+ newFields.add(mergeSourceFieldsToTargetField(targetField, sourceFields)));
return newFields.toArray(DexEncodedField.EMPTY_ARRAY);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 3927d11..d1e98c7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -27,12 +27,10 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
-import com.android.tools.r8.horizontalclassmerging.code.SyntheticClassInitializerConverter;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.shaking.KeepClassInfo;
-import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -40,7 +38,6 @@
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -78,6 +75,7 @@
private ClassMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
Mode mode,
HorizontalClassMergerGraphLens.Builder lensBuilder,
MergeGroup group,
@@ -96,7 +94,7 @@
this.classInitializerMerger = ClassInitializerMerger.create(group);
this.instanceInitializerMergers =
InstanceInitializerMergerCollection.create(
- appView, classIdentifiers, group, classInstanceFieldsMerger, lensBuilder, mode);
+ appView, classIdentifiers, codeProvider, group, lensBuilder, mode);
this.virtualMethodMergers = virtualMethodMergers;
buildClassIdentifierMap();
@@ -109,14 +107,14 @@
void mergeDirectMethods(
SyntheticArgumentClass syntheticArgumentClass,
- SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
- mergeInstanceInitializers(syntheticArgumentClass);
- mergeStaticClassInitializers(syntheticClassInitializerConverterBuilder);
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
+ mergeInstanceInitializers(syntheticArgumentClass, syntheticInitializerConverterBuilder);
+ mergeStaticClassInitializers(syntheticInitializerConverterBuilder);
group.forEach(this::mergeDirectMethods);
}
void mergeStaticClassInitializers(
- SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
if (classInitializerMerger.isEmpty()) {
return;
}
@@ -145,7 +143,7 @@
if (!definition.getCode().isCfCode()) {
assert appView.options().isGeneratingDex();
assert mode.isFinal();
- syntheticClassInitializerConverterBuilder.add(group);
+ syntheticInitializerConverterBuilder.add(new ProgramMethod(group.getTarget(), definition));
}
}
@@ -187,16 +185,20 @@
classMethodsBuilder::isFresh);
}
- void mergeInstanceInitializers(SyntheticArgumentClass syntheticArgumentClass) {
+ void mergeInstanceInitializers(
+ SyntheticArgumentClass syntheticArgumentClass,
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
instanceInitializerMergers.forEach(
- merger -> merger.merge(classMethodsBuilder, syntheticArgumentClass));
+ merger ->
+ merger.merge(
+ classMethodsBuilder, syntheticArgumentClass, syntheticInitializerConverterBuilder));
}
void mergeMethods(
SyntheticArgumentClass syntheticArgumentClass,
- SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
mergeVirtualMethods();
- mergeDirectMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+ mergeDirectMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
classMethodsBuilder.setClassMethods(group.getTarget());
}
@@ -316,51 +318,30 @@
public void mergeGroup(
SyntheticArgumentClass syntheticArgumentClass,
- SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
fixAccessFlags();
fixNestMemberAttributes();
mergeAnnotations();
mergeInterfaces();
mergeFields();
- mergeMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+ mergeMethods(syntheticArgumentClass, syntheticInitializerConverterBuilder);
}
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final IRCodeProvider codeProvider;
private Mode mode;
private final MergeGroup group;
- public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
+ public Builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
+ MergeGroup group,
+ Mode mode) {
this.appView = appView;
+ this.codeProvider = codeProvider;
this.group = group;
- }
-
- Builder setMode(Mode mode) {
this.mode = mode;
- return this;
- }
-
- private void selectTarget() {
- Iterable<DexProgramClass> candidates = Iterables.filter(group, DexClass::isPublic);
- if (IterableUtils.isEmpty(candidates)) {
- candidates = group;
- }
- Iterator<DexProgramClass> candidateIterator = candidates.iterator();
- DexProgramClass target = IterableUtils.first(candidates);
- while (candidateIterator.hasNext()) {
- DexProgramClass current = candidateIterator.next();
- KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
- if (keepClassInfo.isMinificationAllowed(appView.options())) {
- target = current;
- break;
- }
- // Select the target with the shortest name.
- if (current.getType().getDescriptor().size() < target.getType().getDescriptor().size) {
- target = current;
- }
- }
- group.setTarget(
- appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
}
private List<VirtualMethodMerger> createVirtualMethodMergers() {
@@ -393,8 +374,6 @@
public ClassMerger build(
HorizontalClassMergerGraphLens.Builder lensBuilder) {
- selectTarget();
-
List<VirtualMethodMerger> virtualMethodMergers = createVirtualMethodMergers();
boolean requiresClassIdField =
@@ -405,7 +384,7 @@
createClassIdField();
}
- return new ClassMerger(appView, mode, lensBuilder, group, virtualMethodMergers);
+ return new ClassMerger(appView, codeProvider, mode, lensBuilder, group, virtualMethodMergers);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 4ad6f55..666bd64 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.horizontalclassmerging.code.SyntheticClassInitializerConverter;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -65,6 +65,10 @@
if (options.isEnabled(mode)) {
timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
run(runtimeTypeCheckInfo, executorService, timing);
+
+ // Clear type elements cache after IR building.
+ appView.dexItemFactory().clearTypeElementsCache();
+
timing.end();
} else {
appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
@@ -74,8 +78,11 @@
private void run(
RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
throws ExecutionException {
+ IRCodeProvider codeProvider = new IRCodeProvider(appView);
+
// Run the policies on all program classes to produce a final grouping.
- List<Policy> policies = PolicyScheduler.getPolicies(appView, mode, runtimeTypeCheckInfo);
+ List<Policy> policies =
+ PolicyScheduler.getPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo);
Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
// If there are no groups, then end horizontal class merging.
@@ -88,21 +95,20 @@
new HorizontalClassMergerGraphLens.Builder();
// Merge the classes.
- List<ClassMerger> classMergers = initializeClassMergers(lensBuilder, groups);
+ List<ClassMerger> classMergers = initializeClassMergers(codeProvider, lensBuilder, groups);
SyntheticArgumentClass syntheticArgumentClass =
mode.isInitial()
? new SyntheticArgumentClass.Builder(appView.withLiveness()).build(groups)
: null;
- SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder =
- SyntheticClassInitializerConverter.builder(appView);
- applyClassMergers(
- classMergers, syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
+ SyntheticInitializerConverter.builder(appView, codeProvider);
+ applyClassMergers(classMergers, syntheticArgumentClass, syntheticInitializerConverterBuilder);
- SyntheticClassInitializerConverter syntheticClassInitializerConverter =
- syntheticClassInitializerConverterBuilder.build();
- if (!syntheticClassInitializerConverter.isEmpty()) {
+ SyntheticInitializerConverter syntheticInitializerConverter =
+ syntheticInitializerConverterBuilder.build();
+ if (!syntheticInitializerConverter.isEmpty()) {
assert mode.isFinal();
- syntheticClassInitializerConverterBuilder.build().convert(executorService);
+ syntheticInitializerConverterBuilder.build().convert(executorService);
}
// Generate the graph lens.
@@ -194,12 +200,16 @@
* be merged and how the merging should be performed.
*/
private List<ClassMerger> initializeClassMergers(
+ IRCodeProvider codeProvider,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Collection<MergeGroup> groups) {
List<ClassMerger> classMergers = new ArrayList<>(groups.size());
for (MergeGroup group : groups) {
assert group.isNonTrivial();
- classMergers.add(new ClassMerger.Builder(appView, group).setMode(mode).build(lensBuilder));
+ assert group.hasInstanceFieldMap();
+ assert group.hasTarget();
+ classMergers.add(
+ new ClassMerger.Builder(appView, codeProvider, group, mode).build(lensBuilder));
}
return classMergers;
}
@@ -208,13 +218,10 @@
private void applyClassMergers(
Collection<ClassMerger> classMergers,
SyntheticArgumentClass syntheticArgumentClass,
- SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
for (ClassMerger merger : classMergers) {
- merger.mergeGroup(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+ merger.mergeGroup(syntheticArgumentClass, syntheticInitializerConverterBuilder);
}
-
- // Clear type elements cache after IR building.
- appView.dexItemFactory().clearTypeElementsCache();
}
/**
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
new file mode 100644
index 0000000..6976e5a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/IRCodeProvider.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2021, 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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+
+public class IRCodeProvider {
+
+ private final AppView<AppInfo> appViewForConversion;
+
+ public IRCodeProvider(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ // At this point the code rewritings described by repackaging and synthetic finalization have
+ // not been applied to the code objects. These code rewritings will be applied in the
+ // application writer. We therefore simulate that we are in D8, to allow building IR for each of
+ // the class initializers without applying the unapplied code rewritings, to avoid that we apply
+ // the lens more than once to the same piece of code.
+ AppView<AppInfo> appViewForConversion =
+ AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
+ appViewForConversion.setGraphLens(appView.graphLens());
+ this.appViewForConversion = appViewForConversion;
+ }
+
+ public IRCode buildIR(ProgramMethod method) {
+ return method
+ .getDefinition()
+ .getCode()
+ .buildIR(method, appViewForConversion, method.getOrigin());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 9a6390a..bba2edd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -21,20 +21,14 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
-import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
-import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.utils.WorkList;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
@@ -43,36 +37,12 @@
public static InstanceInitializerDescription analyze(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
MergeGroup group,
- ProgramMethod instanceInitializer,
- ClassInstanceFieldsMerger instanceFieldsMerger,
- Mode mode) {
+ ProgramMethod instanceInitializer) {
InstanceInitializerDescription.Builder builder =
InstanceInitializerDescription.builder(appView, instanceInitializer);
-
- if (mode.isFinal()) {
- // TODO(b/189296638): We can't build IR in the final round of class merging without simulating
- // that we're in D8.
- MethodOptimizationInfo optimizationInfo =
- instanceInitializer.getDefinition().getOptimizationInfo();
- if (optimizationInfo.mayHaveSideEffects()) {
- return null;
- }
- InstanceInitializerInfo instanceInitializerInfo =
- optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
- if (!instanceInitializerInfo.hasParent()) {
- // We don't know the parent constructor of the first constructor.
- return null;
- }
- DexMethod parent = instanceInitializerInfo.getParent();
- if (parent.getArity() > 0) {
- return null;
- }
- builder.addInvokeConstructor(parent, ImmutableList.of());
- return builder.build();
- }
-
- IRCode code = instanceInitializer.buildIR(appView);
+ IRCode code = codeProvider.buildIR(instanceInitializer);
WorkList<BasicBlock> workList = WorkList.newIdentityWorkList(code.entryBlock());
while (workList.hasNext()) {
BasicBlock block = workList.next();
@@ -118,12 +88,12 @@
}
InstanceFieldInitializationInfo initializationInfo =
- getInitializationInfo(instancePut.value(), appView, instanceInitializer);
+ getInitializationInfo(appView, instancePut.value());
if (initializationInfo == null) {
return invalid();
}
- ProgramField targetField = instanceFieldsMerger.getTargetField(sourceField);
+ ProgramField targetField = group.getTargetInstanceField(sourceField);
assert targetField != null;
builder.addInstancePut(targetField.getReference(), initializationInfo);
@@ -157,7 +127,7 @@
new ArrayList<>(invoke.arguments().size() - 1);
for (Value argument : Iterables.skip(invoke.arguments(), 1)) {
InstanceFieldInitializationInfo initializationInfo =
- getInitializationInfo(argument, appView, instanceInitializer);
+ getInitializationInfo(appView, argument);
if (initializationInfo == null) {
return invalid();
}
@@ -181,23 +151,28 @@
}
private static InstanceFieldInitializationInfo getInitializationInfo(
- Value value,
- AppView<? extends AppInfoWithClassHierarchy> appView,
- ProgramMethod instanceInitializer) {
- InstanceFieldInitializationInfoFactory factory =
- appView.instanceFieldInitializationInfoFactory();
-
+ AppView<? extends AppInfoWithClassHierarchy> appView, Value value) {
Value root = value.getAliasedValue();
- if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
- return factory.createArgumentInitializationInfo(
- value.getDefinition().asArgument().getIndex());
+ if (root.isPhi()) {
+ return null;
}
- AbstractValue abstractValue = value.getAbstractValue(appView, instanceInitializer);
- if (abstractValue.isSingleConstValue()) {
- return abstractValue.asSingleConstValue();
+ Instruction definition = root.getDefinition();
+ if (definition.isArgument()) {
+ return appView
+ .instanceFieldInitializationInfoFactory()
+ .createArgumentInitializationInfo(root.getDefinition().asArgument().getIndex());
}
-
+ if (definition.isConstNumber()) {
+ return appView
+ .abstractValueFactory()
+ .createSingleNumberValue(definition.asConstNumber().getRawValue());
+ }
+ if (definition.isConstString()) {
+ return appView
+ .abstractValueFactory()
+ .createSingleStringValue(definition.asConstString().getValue());
+ }
return null;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index b67c652..eea6e6b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -16,13 +16,9 @@
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeDirectRange;
-import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -35,7 +31,6 @@
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.IterableUtils;
import com.google.common.collect.ImmutableList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -236,34 +231,6 @@
}
}
- public DexCode createDexCode(
- DexMethod newMethodReference,
- DexMethod originalMethodReference,
- DexMethod syntheticMethodReference,
- MergeGroup group,
- boolean hasClassId,
- int extraNulls) {
- assert !hasClassId;
- assert extraNulls == 0;
- assert parentConstructorArguments.isEmpty();
- assert instanceFieldAssignmentsPre.isEmpty();
- assert instanceFieldAssignmentsPost.isEmpty();
- Instruction[] instructions = new Instruction[2];
- instructions[0] = new InvokeDirectRange(0, 1, parentConstructor);
- instructions[1] = new ReturnVoid();
- int incomingRegisterSize =
- 1 + IterableUtils.sumInt(newMethodReference.getParameters(), DexType::getRequiredRegisters);
- int outgoingRegisterSize = 1;
- return new DexCode(
- incomingRegisterSize,
- incomingRegisterSize,
- outgoingRegisterSize,
- instructions,
- new DexCode.Try[0],
- new DexCode.TryHandler[0],
- null);
- }
-
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 862a23c..e17c574 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.code.ConstructorEntryPointSynthesizedCode;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
import com.android.tools.r8.ir.conversion.ExtraParameter;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
@@ -277,16 +278,7 @@
boolean needsClassId,
int extraNulls) {
if (hasInstanceInitializerDescription()) {
- if (mode.isInitial() || appView.options().isGeneratingClassFiles()) {
- return instanceInitializerDescription.createCfCode(
- newMethodReference,
- getOriginalMethodReference(),
- syntheticMethodReference,
- group,
- needsClassId,
- extraNulls);
- }
- return instanceInitializerDescription.createDexCode(
+ return instanceInitializerDescription.createCfCode(
newMethodReference,
getOriginalMethodReference(),
syntheticMethodReference,
@@ -311,7 +303,8 @@
/** Synthesize a new method which selects the constructor based on a parameter type. */
void merge(
ClassMethodsBuilder classMethodsBuilder,
- SyntheticArgumentClass syntheticArgumentClass) {
+ SyntheticArgumentClass syntheticArgumentClass,
+ SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder) {
ProgramMethod representative = ListUtils.first(instanceInitializers);
// Create merged instance initializer reference.
@@ -378,5 +371,15 @@
true,
getNewClassFileVersion());
classMethodsBuilder.addDirectMethod(newInstanceInitializer);
+
+ if (mode.isFinal()) {
+ if (appView.options().isGeneratingDex() && !newInstanceInitializer.getCode().isDexCode()) {
+ syntheticInitializerConverterBuilder.add(
+ new ProgramMethod(group.getTarget(), newInstanceInitializer));
+ } else {
+ assert !appView.options().isGeneratingClassFiles()
+ || newInstanceInitializer.getCode().isCfCode();
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index b5e485b..dbf0943 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -39,8 +39,8 @@
public static InstanceInitializerMergerCollection create(
AppView<? extends AppInfoWithClassHierarchy> appView,
Reference2IntMap<DexType> classIdentifiers,
+ IRCodeProvider codeProvider,
MergeGroup group,
- ClassInstanceFieldsMerger instanceFieldsMerger,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Mode mode) {
// Create an instance initializer merger for each group of instance initializers that are
@@ -54,7 +54,7 @@
instanceInitializer -> {
InstanceInitializerDescription description =
InstanceInitializerAnalysis.analyze(
- appView, group, instanceInitializer, instanceFieldsMerger, mode);
+ appView, codeProvider, group, instanceInitializer);
if (description != null) {
buildersByDescription
.computeIfAbsent(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index ba5d775..458f0ba 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -6,12 +6,20 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
import com.google.common.collect.Iterables;
import java.util.Collection;
import java.util.Iterator;
@@ -29,6 +37,8 @@
private DexProgramClass target = null;
private Metadata metadata = null;
+ private BidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap;
+
public MergeGroup() {
this.classes = new LinkedList<>();
}
@@ -104,6 +114,31 @@
this.classIdField = classIdField;
}
+ public boolean hasInstanceFieldMap() {
+ return instanceFieldMap != null;
+ }
+
+ public BidirectionalManyToOneMap<DexEncodedField, DexEncodedField> getInstanceFieldMap() {
+ assert hasInstanceFieldMap();
+ return instanceFieldMap;
+ }
+
+ public void selectInstanceFieldMap(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ assert hasTarget();
+ MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap =
+ BidirectionalManyToOneHashMap.newLinkedHashMap();
+ forEachSource(
+ source ->
+ ClassInstanceFieldsMerger.mapFields(appView, source, target, instanceFieldMap::put));
+ setInstanceFieldMap(instanceFieldMap);
+ }
+
+ public void setInstanceFieldMap(
+ BidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap) {
+ assert !hasInstanceFieldMap();
+ this.instanceFieldMap = instanceFieldMap;
+ }
+
public Iterable<DexProgramClass> getSources() {
assert hasTarget();
return Iterables.filter(classes, clazz -> clazz != target);
@@ -122,8 +157,40 @@
return target;
}
- public void setTarget(DexProgramClass target) {
- assert classes.contains(target);
+ public ProgramField getTargetInstanceField(ProgramField field) {
+ assert hasTarget();
+ assert hasInstanceFieldMap();
+ if (field.getHolder() == getTarget()) {
+ return field;
+ }
+ DexEncodedField targetField = getInstanceFieldMap().get(field.getDefinition());
+ return new ProgramField(getTarget(), targetField);
+ }
+
+ public void selectTarget(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ Iterable<DexProgramClass> candidates = Iterables.filter(getClasses(), DexClass::isPublic);
+ if (IterableUtils.isEmpty(candidates)) {
+ candidates = getClasses();
+ }
+ Iterator<DexProgramClass> candidateIterator = candidates.iterator();
+ DexProgramClass target = IterableUtils.first(candidates);
+ while (candidateIterator.hasNext()) {
+ DexProgramClass current = candidateIterator.next();
+ KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
+ if (keepClassInfo.isMinificationAllowed(appView.options())) {
+ target = current;
+ break;
+ }
+ // Select the target with the shortest name.
+ if (current.getType().getDescriptor().size() < target.getType().getDescriptor().size) {
+ target = current;
+ }
+ }
+ setTarget(appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
+ }
+
+ private void setTarget(DexProgramClass target) {
+ assert !hasTarget();
this.target = target;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 6969f94..0d3bf0d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.FinalizeMergeGroup;
import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
@@ -56,12 +57,13 @@
public static List<Policy> getPolicies(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
Mode mode,
RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
List<Policy> policies =
ImmutableList.<Policy>builder()
.addAll(getSingleClassPolicies(appView, mode, runtimeTypeCheckInfo))
- .addAll(getMultiClassPolicies(appView, mode, runtimeTypeCheckInfo))
+ .addAll(getMultiClassPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo))
.build();
assert verifyPolicyOrderingConstraints(policies);
return policies;
@@ -142,6 +144,7 @@
private static List<Policy> getMultiClassPolicies(
AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
Mode mode,
RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
ImmutableList.Builder<Policy> builder = ImmutableList.builder();
@@ -162,16 +165,24 @@
new MinimizeInstanceFieldCasts());
} else {
assert mode.isFinal();
- // TODO(b/185472598): Add support for merging class initializers with dex code.
builder.add(
- new NoInstanceInitializerMerging(mode),
new NoVirtualMethodMerging(appView, mode),
new NoConstructorCollisions(appView, mode));
}
addMultiClassPoliciesForInterfaceMerging(appView, mode, builder);
- return builder.add(new LimitClassGroups(appView)).build();
+ builder.add(new LimitClassGroups(appView));
+
+ if (mode.isFinal()) {
+ // This needs to reason about equivalence of instance initializers, which relies on the
+ // mapping from instance fields on source classes to the instance fields on target classes.
+ // This policy therefore selects a target for each merge group and creates the mapping for
+ // instance fields. For this reason we run this policy in the very end.
+ builder.add(new NoInstanceInitializerMerging(appView, codeProvider, mode));
+ }
+
+ return builder.add(new FinalizeMergeGroup(appView, mode)).build();
}
private static void addRequiredMultiClassPolicies(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
similarity index 66%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index a31333c..d2414ed 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -23,19 +23,24 @@
* Converts synthetic class initializers that have been created as a result of merging class
* initializers into a single class initializer to DEX.
*/
-public class SyntheticClassInitializerConverter {
+public class SyntheticInitializerConverter {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final List<MergeGroup> groups;
+ private final IRCodeProvider codeProvider;
+ private final List<ProgramMethod> methods;
- private SyntheticClassInitializerConverter(
- AppView<? extends AppInfoWithClassHierarchy> appView, List<MergeGroup> groups) {
+ private SyntheticInitializerConverter(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
+ List<ProgramMethod> methods) {
this.appView = appView;
- this.groups = groups;
+ this.codeProvider = codeProvider;
+ this.methods = methods;
}
- public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
- return new Builder(appView);
+ public static Builder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
+ return new Builder(appView, codeProvider);
}
public void convert(ExecutorService executorService) throws ExecutionException {
@@ -51,14 +56,9 @@
// Build IR for each of the class initializers and finalize.
IRConverter converter = new IRConverter(appViewForConversion, Timing.empty());
ThreadUtils.processItems(
- groups,
- group -> {
- ProgramMethod classInitializer = group.getTarget().getProgramClassInitializer();
- IRCode code =
- classInitializer
- .getDefinition()
- .getCode()
- .buildIR(classInitializer, appViewForConversion, classInitializer.getOrigin());
+ methods,
+ method -> {
+ IRCode code = codeProvider.buildIR(method);
converter.removeDeadCodeAndFinalizeIR(
code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
},
@@ -66,25 +66,28 @@
}
public boolean isEmpty() {
- return groups.isEmpty();
+ return methods.isEmpty();
}
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final List<MergeGroup> groups = new ArrayList<>();
+ private final IRCodeProvider codeProvider;
+ private final List<ProgramMethod> methods = new ArrayList<>();
- private Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ private Builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, IRCodeProvider codeProvider) {
this.appView = appView;
+ this.codeProvider = codeProvider;
}
- public Builder add(MergeGroup group) {
- this.groups.add(group);
+ public Builder add(ProgramMethod method) {
+ this.methods.add(method);
return this;
}
- public SyntheticClassInitializerConverter build() {
- return new SyntheticClassInitializerConverter(appView, groups);
+ public SyntheticInitializerConverter build() {
+ return new SyntheticInitializerConverter(appView, codeProvider, methods);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
new file mode 100644
index 0000000..705c8cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
@@ -0,0 +1,75 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Identifies when instance initializer merging is required and bails out. This is needed to ensure
+ * that we don't need to append extra null arguments at constructor call sites, such that the result
+ * of the final round of class merging can be described as a renaming only.
+ *
+ * <p>This policy requires that all instance initializers with the same signature (relaxed, by
+ * converting references types to java.lang.Object) have the same behavior.
+ */
+public class FinalizeMergeGroup extends MultiClassPolicy {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final Mode mode;
+
+ public FinalizeMergeGroup(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ this.appView = appView;
+ this.mode = mode;
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ if (mode.isInitial() || group.isInterfaceGroup()) {
+ group.selectTarget(appView);
+ group.selectInstanceFieldMap(appView);
+ } else {
+ // In the final round of merging each group should be finalized by the
+ // NoInstanceInitializerMerging policy.
+ assert verifyAlreadyFinalized(group);
+ }
+ return ListUtils.newLinkedList(group);
+ }
+
+ @Override
+ public String getName() {
+ return "FinalizeMergeGroup";
+ }
+
+ @Override
+ public boolean isIdentityForInterfaceGroups() {
+ return true;
+ }
+
+ private boolean verifyAlreadyFinalized(MergeGroup group) {
+ assert group.hasTarget();
+ assert group.getClasses().contains(group.getTarget());
+ assert group.hasInstanceFieldMap();
+ Set<DexType> types =
+ SetUtils.newIdentityHashSet(
+ builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
+ group
+ .getInstanceFieldMap()
+ .forEach(
+ (sourceField, targetField) -> {
+ assert types.contains(sourceField.getHolderType());
+ assert types.contains(targetField.getHolderType());
+ });
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
index 5b058c9..191adc4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
@@ -111,9 +111,9 @@
private DexProto rewriteProto(DexProto proto, Map<DexType, MergeGroup> groups) {
DexType[] parameters =
ArrayUtils.map(
- DexType[].class,
proto.getParameters().values,
- parameter -> rewriteType(parameter, groups));
+ parameter -> rewriteType(parameter, groups),
+ DexType.EMPTY_ARRAY);
return dexItemFactory.createProto(rewriteType(proto.getReturnType(), groups), parameters);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
index 0ff172c..02b6bbd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
@@ -4,103 +4,261 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.ClassInstanceFieldsMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
+import com.android.tools.r8.horizontalclassmerging.InstanceInitializerAnalysis;
+import com.android.tools.r8.horizontalclassmerging.InstanceInitializerDescription;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
+import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneMap;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
+import com.google.common.collect.Iterators;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
/**
* Identifies when instance initializer merging is required and bails out. This is needed to ensure
* that we don't need to append extra null arguments at constructor call sites, such that the result
* of the final round of class merging can be described as a renaming only.
+ *
+ * <p>This policy requires that all instance initializers with the same signature (relaxed, by
+ * converting references types to java.lang.Object) have the same behavior.
*/
public class NoInstanceInitializerMerging extends MultiClassPolicy {
- public NoInstanceInitializerMerging(Mode mode) {
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final IRCodeProvider codeProvider;
+
+ public NoInstanceInitializerMerging(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCodeProvider codeProvider,
+ Mode mode) {
assert mode.isFinal();
+ this.appView = appView;
+ this.codeProvider = codeProvider;
}
@Override
public Collection<MergeGroup> apply(MergeGroup group) {
+ assert !group.hasTarget();
+ assert !group.hasInstanceFieldMap();
+
+ if (group.isInterfaceGroup()) {
+ return ListUtils.newLinkedList(group);
+ }
+
+ // When we merge equivalent instance initializers with different protos, we find the least upper
+ // bound of each parameter type. As a result of this, the final instance initializer signatures
+ // are not known until all instance initializers in the group are known. Therefore, we disallow
+ // merging of classes that have multiple methods with the same relaxed method signature (where
+ // reference parameters are converted to java.lang.Object), to ensure that merging will result
+ // in a simple renaming (specifically, we must not need to append null arguments to constructor
+ // calls due to constructor collisions).
+ group.removeIf(this::hasMultipleInstanceInitializersWithSameRelaxedSignature);
+
+ if (group.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // We want to allow merging of equivalent instance initializers. Equivalence depends on the
+ // mapping of instance fields, so we must compute this mapping now.
+ group.selectTarget(appView);
+ group.selectInstanceFieldMap(appView);
+
Map<MergeGroup, Map<DexMethodSignature, ProgramMethod>> newGroups = new LinkedHashMap<>();
- for (DexProgramClass clazz : group) {
- Map<DexMethodSignature, ProgramMethod> classSignatures = new HashMap<>();
- clazz.forEachProgramInstanceInitializer(
- method -> classSignatures.put(method.getMethodSignature(), method));
+ // Caching of instance initializer descriptions, which are used to determine equivalence.
+ // TODO(b/181846319): Make this cache available to the instance initializer merger so that we
+ // don't reanalyze instance initializers.
+ ProgramMethodMap<Optional<InstanceInitializerDescription>> instanceInitializerDescriptions =
+ ProgramMethodMap.create();
+ Function<ProgramMethod, Optional<InstanceInitializerDescription>>
+ instanceInitializerDescriptionProvider =
+ instanceInitializer ->
+ getOrComputeInstanceInitializerDescription(
+ group, instanceInitializer, instanceInitializerDescriptions);
+ // Partition group into smaller groups where there are no (non-equivalent) instance initializer
+ // collisions.
+ for (DexProgramClass clazz : group) {
MergeGroup newGroup = null;
+ Map<DexMethodSignature, ProgramMethod> classInstanceInitializers =
+ getInstanceInitializersByRelaxedSignature(clazz);
for (Entry<MergeGroup, Map<DexMethodSignature, ProgramMethod>> entry : newGroups.entrySet()) {
- Map<DexMethodSignature, ProgramMethod> groupSignatures = entry.getValue();
- if (canAddClassToGroup(classSignatures.values(), groupSignatures)) {
- newGroup = entry.getKey();
- groupSignatures.putAll(classSignatures);
+ MergeGroup candidateGroup = entry.getKey();
+ Map<DexMethodSignature, ProgramMethod> groupInstanceInitializers = entry.getValue();
+ if (canAddClassToGroup(
+ classInstanceInitializers,
+ groupInstanceInitializers,
+ instanceInitializerDescriptionProvider)) {
+ newGroup = candidateGroup;
+ classInstanceInitializers.forEach(groupInstanceInitializers::put);
break;
}
}
-
if (newGroup != null) {
newGroup.add(clazz);
} else {
- newGroups.put(new MergeGroup(clazz), classSignatures);
+ newGroups.put(new MergeGroup(clazz), classInstanceInitializers);
}
}
- return removeTrivialGroups(newGroups.keySet());
+ // Remove trivial groups and finalize the newly created groups.
+ Collection<MergeGroup> newNonTrivialGroups = removeTrivialGroups(newGroups.keySet());
+ setInstanceFieldMaps(newNonTrivialGroups, group);
+ return newNonTrivialGroups;
}
private boolean canAddClassToGroup(
- Iterable<ProgramMethod> classMethods,
- Map<DexMethodSignature, ProgramMethod> groupSignatures) {
- for (ProgramMethod classMethod : classMethods) {
- ProgramMethod groupMethod = groupSignatures.get(classMethod.getMethodSignature());
- if (groupMethod != null && !equivalent(classMethod, groupMethod)) {
+ Map<DexMethodSignature, ProgramMethod> classInstanceInitializers,
+ Map<DexMethodSignature, ProgramMethod> groupInstanceInitializers,
+ Function<ProgramMethod, Optional<InstanceInitializerDescription>>
+ instanceInitializerDescriptionProvider) {
+ for (Entry<DexMethodSignature, ProgramMethod> entry : classInstanceInitializers.entrySet()) {
+ DexMethodSignature relaxedSignature = entry.getKey();
+ ProgramMethod classInstanceInitializer = entry.getValue();
+ ProgramMethod groupInstanceInitializer = groupInstanceInitializers.get(relaxedSignature);
+ if (groupInstanceInitializer == null) {
+ continue;
+ }
+
+ Optional<InstanceInitializerDescription> classInstanceInitializerDescription =
+ instanceInitializerDescriptionProvider.apply(classInstanceInitializer);
+ if (!classInstanceInitializerDescription.isPresent()) {
+ return false;
+ }
+
+ Optional<InstanceInitializerDescription> groupInstanceInitializerDescription =
+ instanceInitializerDescriptionProvider.apply(groupInstanceInitializer);
+ if (!groupInstanceInitializerDescription.isPresent()
+ || !classInstanceInitializerDescription.equals(groupInstanceInitializerDescription)) {
return false;
}
}
return true;
}
- // For now, only recognize constructors with 0 parameters that call the same parent constructor.
- private boolean equivalent(ProgramMethod method, ProgramMethod other) {
- if (!method.getProto().getParameters().isEmpty()) {
+ private boolean hasMultipleInstanceInitializersWithSameRelaxedSignature(DexProgramClass clazz) {
+ Iterator<ProgramMethod> instanceInitializers = clazz.programInstanceInitializers().iterator();
+ if (!instanceInitializers.hasNext()) {
+ // No instance initializers.
return false;
}
- MethodOptimizationInfo optimizationInfo = method.getDefinition().getOptimizationInfo();
- InstanceInitializerInfo instanceInitializerInfo =
- optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
- if (instanceInitializerInfo.isDefaultInstanceInitializerInfo()) {
+ ProgramMethod first = instanceInitializers.next();
+ if (!instanceInitializers.hasNext()) {
+ // Only a single instance initializer.
return false;
}
- InstanceInitializerInfo otherInstanceInitializerInfo =
- other.getDefinition().getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo();
- assert otherInstanceInitializerInfo.isNonTrivialInstanceInitializerInfo();
- if (!instanceInitializerInfo.hasParent()
- || instanceInitializerInfo.getParent().getArity() > 0) {
- return false;
- }
+ Set<DexMethod> seen = SetUtils.newIdentityHashSet(getRelaxedSignature(first));
+ return Iterators.any(
+ instanceInitializers,
+ instanceInitializer -> !seen.add(getRelaxedSignature(instanceInitializer)));
+ }
- if (instanceInitializerInfo.getParent() != otherInstanceInitializerInfo.getParent()) {
- return false;
+ private Map<DexMethodSignature, ProgramMethod> getInstanceInitializersByRelaxedSignature(
+ DexProgramClass clazz) {
+ Map<DexMethodSignature, ProgramMethod> result = new HashMap<>();
+ for (ProgramMethod instanceInitializer : clazz.programInstanceInitializers()) {
+ DexMethodSignature relaxedSignature = getRelaxedSignature(instanceInitializer).getSignature();
+ ProgramMethod previous = result.put(relaxedSignature, instanceInitializer);
+ assert previous == null;
}
+ return result;
+ }
- return !method.getDefinition().getOptimizationInfo().mayHaveSideEffects()
- && !other.getDefinition().getOptimizationInfo().mayHaveSideEffects();
+ private Optional<InstanceInitializerDescription> getOrComputeInstanceInitializerDescription(
+ MergeGroup group,
+ ProgramMethod instanceInitializer,
+ ProgramMethodMap<Optional<InstanceInitializerDescription>> instanceInitializerDescriptions) {
+ return instanceInitializerDescriptions.computeIfAbsent(
+ instanceInitializer,
+ key -> {
+ InstanceInitializerDescription instanceInitializerDescription =
+ InstanceInitializerAnalysis.analyze(
+ appView, codeProvider, group, instanceInitializer);
+ return Optional.ofNullable(instanceInitializerDescription);
+ });
+ }
+
+ private DexMethod getRelaxedSignature(ProgramMethod instanceInitializer) {
+ DexType objectType = appView.dexItemFactory().objectType;
+ DexTypeList parameters = instanceInitializer.getParameters();
+ DexTypeList relaxedParameters =
+ parameters.map(parameter -> parameter.isPrimitiveType() ? parameter : objectType);
+ return parameters != relaxedParameters
+ ? appView
+ .dexItemFactory()
+ .createInstanceInitializer(instanceInitializer.getHolderType(), relaxedParameters)
+ : instanceInitializer.getReference();
+ }
+
+ private void setInstanceFieldMaps(Iterable<MergeGroup> newGroups, MergeGroup group) {
+ for (MergeGroup newGroup : newGroups) {
+ // Set target.
+ newGroup.selectTarget(appView);
+
+ // Construct mapping from instance fields on old target to instance fields on new target.
+ // Note the importance of this: If we create a fresh mapping from the instance fields of each
+ // source class to the new target class, we could invalidate the constructor equivalence.
+ Map<DexEncodedField, DexEncodedField> oldTargetToNewTargetInstanceFieldMap =
+ new IdentityHashMap<>();
+ if (newGroup.getTarget() != group.getTarget()) {
+ ClassInstanceFieldsMerger.mapFields(
+ appView,
+ group.getTarget(),
+ newGroup.getTarget(),
+ oldTargetToNewTargetInstanceFieldMap::put);
+ }
+
+ // Construct mapping from source to target fields.
+ MutableBidirectionalManyToOneMap<DexEncodedField, DexEncodedField> instanceFieldMap =
+ BidirectionalManyToOneHashMap.newLinkedHashMap();
+ newGroup.forEachSource(
+ source ->
+ source.forEachProgramInstanceField(
+ sourceField -> {
+ DexEncodedField oldTargetInstanceField =
+ group.getTargetInstanceField(sourceField).getDefinition();
+ DexEncodedField newTargetInstanceField =
+ oldTargetToNewTargetInstanceFieldMap.getOrDefault(
+ oldTargetInstanceField, oldTargetInstanceField);
+ instanceFieldMap.put(sourceField.getDefinition(), newTargetInstanceField);
+ }));
+ newGroup.setInstanceFieldMap(instanceFieldMap);
+ }
}
@Override
public String getName() {
return "NoInstanceInitializerMerging";
}
+
+ @Override
+ public boolean isIdentityForInterfaceGroups() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 06e1e90..3aa2dac 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
@@ -373,7 +374,7 @@
DexType rawReceiverType = receiverType.getClassType();
if (appInfo.isStrictSubtypeOf(rawReceiverType, references.generatedMessageLiteType)) {
Value dest = code.createValue(receiverType.asMaybeNull(), checkCast.getLocalInfo());
- CheckCast replacement = new CheckCast(dest, checkCast.object(), rawReceiverType);
+ SafeCheckCast replacement = new SafeCheckCast(dest, checkCast.object(), rawReceiverType);
instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 6917a40..2448b0c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -788,8 +788,8 @@
Value receiver = invoke.inValues().get(0);
TypeElement castTypeLattice =
TypeElement.fromDexType(downcast, receiver.getType().nullability(), appView);
- CheckCast castInstruction =
- new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
+ SafeCheckCast castInstruction =
+ new SafeCheckCast(code.createValue(castTypeLattice), receiver, downcast);
castInstruction.setPosition(invoke.getPosition());
// Splice in the check cast operation.
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 3e0f741..b02559a 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
@@ -67,14 +67,13 @@
// we have to insert a move before the check cast instruction.
int inRegister = builder.allocatedRegister(inValues.get(0), getNumber());
if (outValue == null) {
- builder.add(this, new com.android.tools.r8.code.CheckCast(inRegister, type));
+ builder.add(this, createCheckCast(inRegister));
} else {
int outRegister = builder.allocatedRegister(outValue, getNumber());
if (inRegister == outRegister) {
- builder.add(this, new com.android.tools.r8.code.CheckCast(outRegister, type));
+ builder.add(this, createCheckCast(outRegister));
} else {
- com.android.tools.r8.code.CheckCast cast =
- new com.android.tools.r8.code.CheckCast(outRegister, type);
+ com.android.tools.r8.code.CheckCast cast = createCheckCast(outRegister);
if (outRegister <= Constants.U4BIT_MAX && inRegister <= Constants.U4BIT_MAX) {
builder.add(this, new MoveObject(outRegister, inRegister), cast);
} else {
@@ -84,6 +83,10 @@
}
}
+ com.android.tools.r8.code.CheckCast createCheckCast(int register) {
+ return new com.android.tools.r8.code.CheckCast(register, getType());
+ }
+
@Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
return other.isCheckCast() && other.asCheckCast().type == type;
@@ -237,8 +240,8 @@
public static class Builder extends BuilderBase<Builder, CheckCast> {
- private DexType castType;
- private Value object;
+ protected DexType castType;
+ protected Value object;
public Builder setCastType(DexType castType) {
this.castType = castType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 070b8e9..3bdc40c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -37,14 +37,12 @@
}
@Override
- public void setOutValue(Value value) {
- assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
- value instanceof StackValues;
- this.outValue = value;
- this.outValue.definition = this;
- for (StackValue val : ((StackValues)value).getStackValues()) {
- val.definition = this;
+ public Value setOutValue(Value newOutValue) {
+ assert newOutValue instanceof StackValues;
+ for (StackValue stackValue : ((StackValues) newOutValue).getStackValues()) {
+ stackValue.definition = this;
}
+ return super.setOutValue(newOutValue);
}
private StackValue[] getStackValues() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 7053383..a01b789 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -51,14 +51,12 @@
}
@Override
- public void setOutValue(Value value) {
- assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
- value instanceof StackValues;
- this.outValue = value;
- this.outValue.definition = this;
- for (StackValue val : ((StackValues)value).getStackValues()) {
- val.definition = this;
+ public Value setOutValue(Value newOutValue) {
+ assert newOutValue instanceof StackValues;
+ for (StackValue stackValue : ((StackValues) newOutValue).getStackValues()) {
+ stackValue.definition = this;
}
+ return super.setOutValue(newOutValue);
}
private StackValue[] getStackValues() {
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 a1c7d45..708ffcf 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
@@ -141,12 +141,18 @@
return outValue;
}
- public void setOutValue(Value value) {
- assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed();
- outValue = value;
- if (outValue != null) {
- outValue.definition = this;
+ public Value clearOutValue() {
+ return setOutValue(null);
+ }
+
+ // Returns the previous out-value, if any.
+ public Value setOutValue(Value newOutValue) {
+ Value previousOutValue = outValue();
+ outValue = newOutValue;
+ if (newOutValue != null) {
+ newOutValue.definition = this;
}
+ return previousOutValue;
}
public Value swapOutValue(Value newOutValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java b/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
new file mode 100644
index 0000000..8b36c90
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/SafeCheckCast.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2021, 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.cf.code.CfSafeCheckCast;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+
+public class SafeCheckCast extends CheckCast {
+
+ public SafeCheckCast(Value dest, Value value, DexType type) {
+ super(dest, value, type);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public void buildCf(CfBuilder builder) {
+ builder.add(new CfSafeCheckCast(getType()));
+ }
+
+ @Override
+ com.android.tools.r8.code.CheckCast createCheckCast(int register) {
+ return new com.android.tools.r8.code.SafeCheckCast(register, getType());
+ }
+
+ @Override
+ public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
+ return false;
+ }
+
+ public static class Builder extends CheckCast.Builder {
+
+ @Override
+ public CheckCast build() {
+ return amend(new SafeCheckCast(outValue, object, castType));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 5dfa623..777eec1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -40,14 +40,12 @@
}
@Override
- public void setOutValue(Value value) {
- assert outValue == null || !outValue.hasUsersInfo() || !outValue.isUsed() ||
- value instanceof StackValues;
- this.outValue = value;
- this.outValue.definition = this;
- for (StackValue val : ((StackValues)value).getStackValues()) {
- val.definition = this;
+ public Value setOutValue(Value newOutValue) {
+ assert newOutValue instanceof StackValues;
+ for (StackValue stackValue : ((StackValues) newOutValue).getStackValues()) {
+ stackValue.definition = this;
}
+ return super.setOutValue(newOutValue);
}
private StackValue[] getStackValues() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index e01cd47..5a492f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -134,7 +134,7 @@
this.code = code;
}
- public CfCode build(DeadCodeRemover deadCodeRemover) {
+ public CfCode build(DeadCodeRemover deadCodeRemover, MethodConversionOptions conversionOptions) {
computeInitializers();
TypeVerificationHelper typeVerificationHelper = new TypeVerificationHelper(appView, code);
typeVerificationHelper.computeVerificationTypes();
@@ -168,10 +168,13 @@
loadStoreHelper.insertPhiMoves(registerAllocator);
- for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
- CodeRewriter.collapseTrivialGotos(code);
- PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
- PeepholeOptimizer.shareIdenticalBlockSuffix(code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
+ if (conversionOptions.isPeepholeOptimizationsEnabled()) {
+ for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
+ CodeRewriter.collapseTrivialGotos(code);
+ PeepholeOptimizer.removeIdenticalPredecessorBlocks(code, registerAllocator);
+ PeepholeOptimizer.shareIdenticalBlockSuffix(
+ code, registerAllocator, SUFFIX_SHARING_OVERHEAD);
+ }
}
rewriteIincPatterns();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 4ae55f6..ba1a15c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -102,6 +102,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Rem;
import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.Shl;
import com.android.tools.r8.ir.code.Shr;
import com.android.tools.r8.ir.code.StaticGet;
@@ -1181,11 +1182,20 @@
}
public void addCheckCast(int value, DexType type) {
+ internalAddCheckCast(value, type, false);
+ }
+
+ public void addSafeCheckCast(int value, DexType type) {
+ internalAddCheckCast(value, type, true);
+ }
+
+ private void internalAddCheckCast(int value, DexType type, boolean isSafe) {
Value in = readRegister(value, ValueTypeConstraint.OBJECT);
TypeElement castTypeLattice =
TypeElement.fromDexType(type, in.getType().nullability(), appView);
Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
- CheckCast instruction = new CheckCast(out, in, type);
+ CheckCast instruction =
+ isSafe ? new SafeCheckCast(out, in, type) : new CheckCast(out, in, type);
assert instruction.instructionTypeCanThrow();
add(instruction);
}
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 8ae52ef..f68c904 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
@@ -42,6 +42,8 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.DefaultMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfClassDesugaringEventConsumer.D8CfClassDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
@@ -57,6 +59,7 @@
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.AssumeInserter;
+import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -670,6 +673,15 @@
// 2) Revisit DexEncodedMethods for the collected candidates.
printPhase("Primary optimization pass");
+
+ appView.withCallSiteOptimizationInfoPropagator(
+ optimization -> {
+ optimization.abandonCallSitePropagationForLambdaImplementationMethods(
+ executorService, timing);
+ optimization.abandonCallSitePropagationForPinnedMethodsAndOverrides(
+ executorService, timing);
+ });
+
if (fieldAccessAnalysis != null) {
fieldAccessAnalysis.fieldAssignmentTracker().initialize();
}
@@ -678,8 +690,7 @@
GraphLens initialGraphLensForIR = appView.graphLens();
GraphLens graphLensForIR = initialGraphLensForIR;
OptimizationFeedbackDelayed feedback = delayedOptimizationFeedback;
- PostMethodProcessor.Builder postMethodProcessorBuilder =
- new PostMethodProcessor.Builder(getOptimizationsForPostIRProcessing());
+ PostMethodProcessor.Builder postMethodProcessorBuilder = new PostMethodProcessor.Builder();
{
timing.begin("Build primary method processor");
PrimaryMethodProcessor primaryMethodProcessor =
@@ -721,9 +732,9 @@
// 1) Second pass for methods whose collected call site information become more precise.
// 2) Second inlining pass for dealing with double inline callers.
printPhase("Post optimization pass");
- if (appView.callSiteOptimizationInfoPropagator() != null) {
- postMethodProcessorBuilder.put(appView.callSiteOptimizationInfoPropagator());
- }
+ appView.withCallSiteOptimizationInfoPropagator(
+ optimization ->
+ postMethodProcessorBuilder.put(appView.callSiteOptimizationInfoPropagator()));
if (inliner != null) {
postMethodProcessorBuilder.put(inliner);
}
@@ -742,7 +753,12 @@
postMethodProcessorBuilder.build(appView, executorService, timing);
if (postMethodProcessor != null) {
assert !options.debug;
- postMethodProcessor.forEachWaveWithExtension(feedback, executorService);
+ postMethodProcessor.forEachMethod(
+ (method, methodProcessingContext) ->
+ processDesugaredMethod(
+ method, feedback, postMethodProcessor, methodProcessingContext),
+ feedback,
+ executorService);
feedback.updateVisibleOptimizationInfo();
assert graphLensForIR == appView.graphLens();
}
@@ -832,9 +848,8 @@
}
if (Log.ENABLED) {
- if (appView.callSiteOptimizationInfoPropagator() != null) {
- appView.callSiteOptimizationInfoPropagator().logResults();
- }
+ appView.withCallSiteOptimizationInfoPropagator(
+ CallSiteOptimizationInfoPropagator::logResults);
constantCanonicalizer.logResults();
if (idempotentFunctionCallCanonicalizer != null) {
idempotentFunctionCallCanonicalizer.logResults();
@@ -981,7 +996,9 @@
Timing timing = Timing.empty();
deadCodeRemover.run(code, timing);
code.traceBlocks();
- RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+ RegisterAllocator registerAllocator =
+ performRegisterAllocation(
+ code, method, DefaultMethodConversionOptions.getInstance(), timing);
method.setCode(code, registerAllocator, appView);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
@@ -1168,6 +1185,8 @@
ProgramMethod context = code.context();
DexEncodedMethod method = context.getDefinition();
DexProgramClass holder = context.getHolder();
+ MutableMethodConversionOptions conversionOptions =
+ new MutableMethodConversionOptions(methodProcessor);
assert holder != null;
Timing timing = Timing.create(method.qualifiedName(), options);
@@ -1237,7 +1256,13 @@
assert appView.enableWholeProgramOptimizations();
timing.begin("Collect optimization info");
collectOptimizationInfo(
- context, code, ClassInitializerDefaultsResult.empty(), feedback, methodProcessor, timing);
+ context,
+ code,
+ ClassInitializerDefaultsResult.empty(),
+ feedback,
+ methodProcessor,
+ conversionOptions,
+ timing);
timing.end();
return timing;
}
@@ -1566,7 +1591,13 @@
if (appView.enableWholeProgramOptimizations()) {
timing.begin("Collect optimization info");
collectOptimizationInfo(
- context, code, classInitializerDefaultsResult, feedback, methodProcessor, timing);
+ context,
+ code,
+ classInitializerDefaultsResult,
+ feedback,
+ methodProcessor,
+ conversionOptions,
+ timing);
timing.end();
}
@@ -1593,7 +1624,7 @@
printMethod(code, "Optimized IR (SSA)", previous);
timing.begin("Finalize IR");
- finalizeIR(code, feedback, timing);
+ finalizeIR(code, feedback, conversionOptions, timing);
timing.end();
return timing;
}
@@ -1606,9 +1637,11 @@
ClassInitializerDefaultsResult classInitializerDefaultsResult,
OptimizationFeedback feedback,
MethodProcessor methodProcessor,
+ MutableMethodConversionOptions conversionOptions,
Timing timing) {
+
if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
- enumUnboxer.analyzeEnums(code);
+ enumUnboxer.analyzeEnums(code, conversionOptions);
}
if (libraryMethodOverrideAnalysis != null) {
@@ -1671,16 +1704,20 @@
stringSwitchRemover.run(code);
}
deadCodeRemover.run(code, timing);
- finalizeIR(code, feedback, timing);
+ finalizeIR(code, feedback, DefaultMethodConversionOptions.getInstance(), timing);
}
- public void finalizeIR(IRCode code, OptimizationFeedback feedback, Timing timing) {
+ public void finalizeIR(
+ IRCode code,
+ OptimizationFeedback feedback,
+ MethodConversionOptions conversionOptions,
+ Timing timing) {
code.traceBlocks();
if (options.isGeneratingClassFiles()) {
- finalizeToCf(code, feedback);
+ finalizeToCf(code, feedback, conversionOptions);
} else {
assert options.isGeneratingDex();
- finalizeToDex(code, feedback, timing);
+ finalizeToDex(code, feedback, conversionOptions, timing);
}
}
@@ -1691,23 +1728,29 @@
feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
}
- private void finalizeToCf(IRCode code, OptimizationFeedback feedback) {
+ private void finalizeToCf(
+ IRCode code, OptimizationFeedback feedback, MethodConversionOptions conversionOptions) {
DexEncodedMethod method = code.method();
assert !method.getCode().isDexCode();
CfBuilder builder = new CfBuilder(appView, method, code);
- CfCode result = builder.build(deadCodeRemover);
+ CfCode result = builder.build(deadCodeRemover, conversionOptions);
method.setCode(result, appView);
markProcessed(code, feedback);
}
- private void finalizeToDex(IRCode code, OptimizationFeedback feedback, Timing timing) {
+ private void finalizeToDex(
+ IRCode code,
+ OptimizationFeedback feedback,
+ MethodConversionOptions conversionOptions,
+ Timing timing) {
DexEncodedMethod method = code.method();
// Workaround massive dex2oat memory use for self-recursive methods.
CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
// Workaround MAX_INT switch issue.
codeRewriter.rewriteSwitchForMaxInt(code);
// Perform register allocation.
- RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
+ RegisterAllocator registerAllocator =
+ performRegisterAllocation(code, method, conversionOptions, timing);
timing.begin("Build DEX code");
method.setCode(code, registerAllocator, appView);
timing.end();
@@ -1759,7 +1802,10 @@
}
private RegisterAllocator performRegisterAllocation(
- IRCode code, DexEncodedMethod method, Timing timing) {
+ IRCode code,
+ DexEncodedMethod method,
+ MethodConversionOptions conversionOptions,
+ Timing timing) {
// Always perform dead code elimination before register allocation. The register allocator
// does not allow dead code (to make sure that we do not waste registers for unneeded values).
assert deadCodeRemover.verifyNoDeadCode(code);
@@ -1773,12 +1819,14 @@
codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
}
printMethod(code, "After register allocation (non-SSA)", null);
- timing.begin("Peephole optimize");
- for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
- CodeRewriter.collapseTrivialGotos(code);
- PeepholeOptimizer.optimize(code, registerAllocator);
+ if (conversionOptions.isPeepholeOptimizationsEnabled()) {
+ timing.begin("Peephole optimize");
+ for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
+ CodeRewriter.collapseTrivialGotos(code);
+ PeepholeOptimizer.optimize(code, registerAllocator);
+ }
+ timing.end();
}
- timing.end();
timing.begin("Clean up");
CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
CodeRewriter.collapseTrivialGotos(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index b02d5e7..f44ec67 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -86,6 +86,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
@@ -415,7 +416,7 @@
Value castOutValue = code.createValue(castType);
newOutValue.replaceUsers(castOutValue);
CheckCast checkCast =
- CheckCast.builder()
+ SafeCheckCast.builder()
.setCastType(lookup.getCastType())
.setObject(newOutValue)
.setOutValue(castOutValue)
@@ -479,7 +480,7 @@
Value castOutValue = code.createValue(castType);
newOutValue.replaceUsers(castOutValue);
CheckCast checkCast =
- CheckCast.builder()
+ SafeCheckCast.builder()
.setCastType(lookup.getCastType())
.setObject(newOutValue)
.setOutValue(castOutValue)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
new file mode 100644
index 0000000..1dd9510
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodConversionOptions.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, 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.conversion;
+
+public abstract class MethodConversionOptions {
+
+ public abstract boolean isPeepholeOptimizationsEnabled();
+
+ public static class MutableMethodConversionOptions extends MethodConversionOptions {
+
+ private final MethodProcessor methodProcessor;
+ private boolean enablePeepholeOptimizations = true;
+
+ public MutableMethodConversionOptions(MethodProcessor methodProcessor) {
+ this.methodProcessor = methodProcessor;
+ }
+
+ public void disablePeepholeOptimizations() {
+ assert methodProcessor.isPrimaryMethodProcessor();
+ enablePeepholeOptimizations = false;
+ }
+
+ @Override
+ public boolean isPeepholeOptimizationsEnabled() {
+ assert enablePeepholeOptimizations || methodProcessor.isPrimaryMethodProcessor();
+ return enablePeepholeOptimizations;
+ }
+ }
+
+ public static class DefaultMethodConversionOptions extends MethodConversionOptions {
+
+ private static final DefaultMethodConversionOptions INSTANCE =
+ new DefaultMethodConversionOptions();
+
+ private DefaultMethodConversionOptions() {}
+
+ public static DefaultMethodConversionOptions getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isPeepholeOptimizationsEnabled() {
+ return true;
+ }
+ }
+}
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 21608cf..9d9b9f1 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
@@ -6,6 +6,7 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -13,9 +14,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -25,33 +23,22 @@
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import java.util.ArrayDeque;
-import java.util.Collection;
import java.util.Deque;
-import java.util.IdentityHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
public class PostMethodProcessor extends MethodProcessorWithWave {
private final ProcessorContext processorContext;
- private final AppView<AppInfoWithLiveness> appView;
- private final Collection<CodeOptimization> defaultCodeOptimizations;
- private final Map<DexMethod, Collection<CodeOptimization>> methodsMap;
private final Deque<SortedProgramMethodSet> waves;
private final ProgramMethodSet processed = ProgramMethodSet.create();
private PostMethodProcessor(
AppView<AppInfoWithLiveness> appView,
- Collection<CodeOptimization> defaultCodeOptimizations,
- Map<DexMethod, Collection<CodeOptimization>> methodsMap,
CallGraph callGraph) {
this.processorContext = appView.createProcessorContext();
- this.appView = appView;
- this.defaultCodeOptimizations = defaultCodeOptimizations;
- this.methodsMap = methodsMap;
this.waves = createWaves(callGraph);
}
@@ -63,58 +50,28 @@
public static class Builder {
- private final Collection<CodeOptimization> defaultCodeOptimizations;
- private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
+ private final LongLivedProgramMethodSetBuilder<?> methodsToReprocessBuilder =
LongLivedProgramMethodSetBuilder.createForIdentitySet();
- private final Map<DexMethod, Collection<CodeOptimization>> optimizationsMap =
- new IdentityHashMap<>();
- Builder(Collection<CodeOptimization> defaultCodeOptimizations) {
- this.defaultCodeOptimizations = defaultCodeOptimizations;
- }
+ Builder() {}
- private void put(
- ProgramMethodSet methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
- if (codeOptimizations.isEmpty()) {
- // Nothing to conduct.
- return;
- }
- for (ProgramMethod method : methodsToRevisit) {
- methodsToReprocess.add(method);
- optimizationsMap
- .computeIfAbsent(
- method.getReference(),
- // Optimization order might matter, hence a collection that preserves orderings.
- k -> new LinkedHashSet<>())
- .addAll(codeOptimizations);
- }
+ public void add(ProgramMethod method) {
+ methodsToReprocessBuilder.add(method);
}
public void put(ProgramMethodSet methodsToRevisit) {
- put(methodsToRevisit, defaultCodeOptimizations);
+ methodsToRevisit.forEach(this::add);
}
public void put(PostOptimization postOptimization) {
- Collection<CodeOptimization> codeOptimizations =
- postOptimization.codeOptimizationsForPostProcessing();
- if (codeOptimizations == null) {
- codeOptimizations = defaultCodeOptimizations;
- }
- put(postOptimization.methodsToRevisit(), codeOptimizations);
+ put(postOptimization.methodsToRevisit());
}
// 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 rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens applied) {
- methodsToReprocess.rewrittenWithLens(appView, applied);
- Map<DexMethod, Collection<CodeOptimization>> newOptimizationsMap = new IdentityHashMap<>();
- optimizationsMap.forEach(
- (method, optimizations) ->
- newOptimizationsMap.put(
- appView.graphLens().getRenamedMethodSignature(method, applied), optimizations));
- optimizationsMap.clear();
- optimizationsMap.putAll(newOptimizationsMap);
+ methodsToReprocessBuilder.rewrittenWithLens(appView, applied);
}
PostMethodProcessor build(
@@ -133,16 +90,15 @@
});
put(set);
}
- if (methodsToReprocess.isEmpty()) {
+ if (methodsToReprocessBuilder.isEmpty()) {
// Nothing to revisit.
return null;
}
+ ProgramMethodSet methodsToReprocess =
+ methodsToReprocessBuilder.build(appView, appView.graphLens());
CallGraph callGraph =
- new PartialCallGraphBuilder(
- appView, methodsToReprocess.build(appView, appView.graphLens()))
- .build(executorService, timing);
- return new PostMethodProcessor(
- appView, defaultCodeOptimizations, optimizationsMap, callGraph);
+ new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing);
+ return new PostMethodProcessor(appView, callGraph);
}
}
@@ -159,18 +115,10 @@
return waves;
}
- @Override
- protected void prepareForWaveExtensionProcessing() {
- waveExtension.forEach(
- method -> {
- assert !methodsMap.containsKey(method.getReference());
- methodsMap.put(method.getReference(), defaultCodeOptimizations);
- });
- super.prepareForWaveExtensionProcessing();
- }
-
- void forEachWaveWithExtension(
- OptimizationFeedbackDelayed feedback, ExecutorService executorService)
+ void forEachMethod(
+ BiConsumer<ProgramMethod, MethodProcessingContext> consumer,
+ OptimizationFeedbackDelayed feedback,
+ ExecutorService executorService)
throws ExecutionException {
while (!waves.isEmpty()) {
wave = waves.removeFirst();
@@ -180,12 +128,8 @@
assert feedback.noUpdatesLeft();
ThreadUtils.processItems(
wave,
- method -> {
- Collection<CodeOptimization> codeOptimizations =
- methodsMap.get(method.getReference());
- assert codeOptimizations != null && !codeOptimizations.isEmpty();
- forEachMethod(method, codeOptimizations, feedback);
- },
+ method ->
+ consumer.accept(method, processorContext.createMethodProcessingContext(method)),
executorService);
feedback.updateVisibleOptimizationInfo();
processed.addAll(wave);
@@ -193,26 +137,4 @@
} while (!wave.isEmpty());
}
}
-
- private void forEachMethod(
- ProgramMethod method,
- Collection<CodeOptimization> codeOptimizations,
- OptimizationFeedback feedback) {
- // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
- // Then, we can share IRCode creation there.
- if (appView.options().skipIR) {
- feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
- return;
- }
- IRCode code = method.buildIR(appView);
- if (code == null) {
- feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
- return;
- }
- // TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
- for (CodeOptimization codeOptimization : codeOptimizations) {
- codeOptimization.optimize(
- code, feedback, this, processorContext.createMethodProcessingContext(method));
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
index 7f06207..30c873d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import java.util.Collection;
/**
* An abstraction of optimizations that require post processing of methods.
@@ -13,16 +12,4 @@
/** @return a set of methods that need post processing. */
ProgramMethodSet methodsToRevisit();
-
- // TODO(b/127694949): different CodeOptimization for primary processor v.s. post processor?
- // In that way, instead of internal state changes, such as COLLECT v.s. APPLY or REVISIT,
- // optimizers that need post processing can return what to do at each processor.
- // Collection<CodeOptimization> codeOptimizationsForPrimaryProcessing();
-
- /**
- * @return specific collection of {@link CodeOptimization}s to conduct during post processing.
- * Otherwise, i.e., if the default one---IRConverter's full processing---is okay,
- * returns {@code null}.
- */
- Collection<CodeOptimization> codeOptimizationsForPostProcessing();
}
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 3eada1f..021ffa2 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
@@ -977,6 +977,17 @@
DexProto proto;
DexMethod method;
+ // List
+ type = factory.listType;
+
+ // List List.copyOf(Collection)
+ name = factory.createString("copyOf");
+ proto = factory.createProto(factory.listType, factory.collectionType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new MethodGenerator(
+ method, BackportedMethods::CollectionsMethods_copyOfList, "copyOfList"));
+
// Set
type = factory.setType;
@@ -1133,16 +1144,7 @@
}
private void initializeJava10MethodProviders(DexItemFactory factory) {
- // List
- DexType type = factory.listType;
-
- // List List.copyOf(Collection)
- DexString name = factory.createString("copyOf");
- DexProto proto = factory.createProto(factory.listType, factory.collectionType);
- DexMethod method = factory.createMethod(type, proto, name);
- addProvider(
- new MethodGenerator(
- method, BackportedMethods::CollectionsMethods_copyOfList, "copyOfList"));
+ // Nothing right now.
}
private void initializeJava11MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index cf86667..30fc05c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -245,6 +245,8 @@
companionMethod.name)));
}
}
+ } else {
+ assert extraDispatchCases.size() <= 1;
}
return extraDispatchCases;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 79afbf8..ae334dc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.InvalidCode;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
@@ -858,9 +859,9 @@
assert resolutionResult != null;
assert resolutionResult.getResolvedMethod().isStatic();
assert invokeNeedsRewriting(invokedMethod, STATIC);
-
- return rewriteInvoke.apply(
- staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
+ DexClassAndMethod companionMethod =
+ ensureStaticAsMethodOfCompanionClassStub(resolutionResult.getResolutionPair());
+ return rewriteInvoke.apply(companionMethod.getReference());
}
private Collection<CfInstruction> rewriteInvokeSuper(
@@ -1187,12 +1188,21 @@
return factory.createType(interfaceTypeDescriptor);
}
+ DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(DexClassAndMethod method) {
+ if (method.isProgramMethod()) {
+ return ensureStaticAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+ } else {
+ ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
+ DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
+ return ensureMethodOfClasspathCompanionClassStub(companionMethodReference, context, appView);
+ }
+ }
+
// Represent a static interface method as a method of companion class.
final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
- recordCompanionClassReference(appView, method, rewritten);
return rewritten;
}
@@ -1244,19 +1254,24 @@
return privateAsMethodOfCompanionClass(method.getReference(), factory);
}
- private static void recordCompanionClassReference(
+ private static DexClassAndMethod recordCompanionClassReference(
AppView<?> appView, DexClassAndMethod method, DexMethod rewritten) {
ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
// If the interface class is a program class, we shouldn't need to synthesize the companion
// class on the classpath.
if (context == null) {
- return;
+ return null;
}
- appView
+ return ensureMethodOfClasspathCompanionClassStub(rewritten, context, appView);
+ }
+
+ private static DexClassAndMethod ensureMethodOfClasspathCompanionClassStub(
+ DexMethod companionMethodReference, ClasspathOrLibraryClass context, AppView<?> appView) {
+ return appView
.getSyntheticItems()
.ensureFixedClasspathClassMethod(
- rewritten.getName(),
- rewritten.getProto(),
+ companionMethodReference.getName(),
+ companionMethodReference.getProto(),
SyntheticKind.COMPANION_CLASS,
context,
appView,
@@ -1267,6 +1282,32 @@
.setCode(DexEncodedMethod::buildEmptyThrowingCfCode));
}
+ ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(ProgramMethod method) {
+ DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
+ DexEncodedMethod definition = method.getDefinition();
+ return InterfaceProcessor.ensureCompanionMethod(
+ method.getHolder(),
+ companionMethodReference.getName(),
+ companionMethodReference.getProto(),
+ appView,
+ methodBuilder -> {
+ MethodAccessFlags newFlags = definition.getAccessFlags().copy();
+ newFlags.promoteToPublic();
+ methodBuilder
+ .setAccessFlags(newFlags)
+ .setGenericSignature(definition.getGenericSignature())
+ .setAnnotations(definition.annotations())
+ .setParameterAnnotationsList(definition.getParameterAnnotations())
+ // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
+ // code to ensure it is never used before desugared and installed.
+ .setCode(
+ m ->
+ appView.enableWholeProgramOptimizations()
+ ? definition.getCode()
+ : InvalidCode.getInstance());
+ });
+ }
+
/**
* Move static and default interface methods to companion classes, add missing methods to forward
* to moved default methods implementation.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 2174002..ab61be0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InvalidCode;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodCollection;
import com.android.tools.r8.graph.NestedGraphLens;
@@ -74,12 +75,10 @@
private final InterfaceMethodRewriter rewriter;
private final Map<DexProgramClass, PostProcessingInterfaceInfo> postProcessingInterfaceInfos =
new ConcurrentHashMap<>();
- private final ClassTypeSignature objectTypeSignature;
InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
this.appView = appView;
this.rewriter = rewriter;
- this.objectTypeSignature = new ClassTypeSignature(appView.dexItemFactory().objectType);
}
@Override
@@ -111,30 +110,29 @@
processDirectInterfaceMethods(iface);
}
- private ProgramMethod ensureCompanionMethod(
+ static ProgramMethod ensureCompanionMethod(
DexProgramClass iface,
DexString methodName,
DexProto methodProto,
+ AppView<?> appView,
Consumer<SyntheticMethodBuilder> fn) {
- ProgramMethod method =
- appView
- .getSyntheticItems()
- .ensureFixedClassMethod(
- methodName,
- methodProto,
- SyntheticKind.COMPANION_CLASS,
- iface,
- appView,
- builder ->
- builder
- .setSourceFile(iface.sourceFile)
- .setGenericSignature(
- iface
- .getClassSignature()
- .toObjectBoundWithSameFormals(objectTypeSignature)),
- fn);
- assert method.getHolderType() == rewriter.getCompanionClassType(iface.type);
- return method;
+ return appView
+ .getSyntheticItems()
+ .ensureFixedClassMethod(
+ methodName,
+ methodProto,
+ SyntheticKind.COMPANION_CLASS,
+ iface,
+ appView,
+ builder ->
+ builder
+ .setSourceFile(iface.sourceFile)
+ .setGenericSignature(
+ iface
+ .getClassSignature()
+ .toObjectBoundWithSameFormals(
+ new ClassTypeSignature(appView.dexItemFactory().objectType))),
+ fn);
}
private void ensureCompanionClassInitializesInterface(
@@ -148,6 +146,7 @@
iface,
appView.dexItemFactory().classConstructorMethodName,
appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
+ appView,
methodBuilder -> createCompanionClassInitializer(iface, clinitField, methodBuilder));
synthesizedMethods.add(clinit);
}
@@ -252,12 +251,17 @@
iface,
companionMethod.getName(),
companionMethod.getProto(),
+ appView,
methodBuilder ->
methodBuilder
.setAccessFlags(newFlags)
.setGenericSignature(MethodTypeSignature.noSignature())
- .setAnnotations(virtual.annotations())
- .setParameterAnnotationsList(virtual.getParameterAnnotations())
+ .setAnnotations(
+ virtual
+ .annotations()
+ .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
+ .setParameterAnnotationsList(
+ virtual.getParameterAnnotations().withFakeThisParameter())
.setCode(ignored -> virtual.getCode())
.setOnBuildConsumer(
implMethod -> {
@@ -298,25 +302,17 @@
+ " is expected to "
+ "either be public or private in "
+ iface.origin;
- DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(method);
-
- ensureCompanionMethod(
- iface,
- companionMethod.getName(),
- companionMethod.getProto(),
- methodBuilder ->
- methodBuilder
- .setAccessFlags(newFlags)
- .setGenericSignature(definition.getGenericSignature())
- .setAnnotations(definition.annotations())
- .setParameterAnnotationsList(definition.getParameterAnnotations())
- .setCode(ignored -> definition.getCode())
- .setOnBuildConsumer(
- implMethod -> {
- implMethod.copyMetadata(definition);
- }));
-
- getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companionMethod);
+ ProgramMethod companion = rewriter.ensureStaticAsMethodOfProgramCompanionClassStub(method);
+ // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code
+ // moves.
+ assert appView.enableWholeProgramOptimizations()
+ || InvalidCode.isInvalidCode(companion.getDefinition().getCode());
+ if (definition.hasClassFileVersion()) {
+ companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
+ }
+ companion.getDefinition().setCode(definition.getCode(), appView);
+ getPostProcessingInterfaceInfo(iface).moveMethod(oldMethod, companion.getReference());
+ definition.setCode(InvalidCode.getInstance(), appView);
continue;
}
@@ -343,6 +339,7 @@
iface,
companionMethod.getName(),
companionMethod.getProto(),
+ appView,
methodBuilder ->
methodBuilder
.setAccessFlags(newFlags)
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 2fdd282..5a30d1d 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
@@ -27,7 +27,6 @@
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.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
@@ -38,13 +37,16 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
import com.android.tools.r8.utils.LazyBox;
+import com.android.tools.r8.utils.ThreadUtils;
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;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
public class CallSiteOptimizationInfoPropagator implements PostOptimization {
@@ -95,10 +97,6 @@
return;
}
- if (appView.appInfo().isMethodTargetedByInvokeDynamic(code.context().getReference())) {
- abandonCallSitePropagationForMethodAndOverrides(code.context());
- }
-
ProgramMethod context = code.context();
for (Instruction instruction : code.instructions()) {
if (instruction.isInvokeMethod()) {
@@ -291,6 +289,51 @@
}
}
+ public void abandonCallSitePropagationForLambdaImplementationMethods(
+ ExecutorService executorService, Timing timing) throws ExecutionException {
+ if (appView.options().isGeneratingClassFiles()) {
+ timing.begin("Call site optimization: abandon lambda implementation methods");
+ ForEachable<ProgramMethod> lambdaImplementationMethods =
+ consumer ->
+ appView
+ .appInfo()
+ .forEachMethod(
+ method -> {
+ if (appView
+ .appInfo()
+ .isMethodTargetedByInvokeDynamic(method.getReference())) {
+ consumer.accept(method);
+ }
+ });
+ ThreadUtils.processItems(
+ lambdaImplementationMethods,
+ this::abandonCallSitePropagationForMethodAndOverrides,
+ executorService);
+ timing.end();
+ }
+ }
+
+ public void abandonCallSitePropagationForPinnedMethodsAndOverrides(
+ ExecutorService executorService, Timing timing) throws ExecutionException {
+ timing.begin("Call site optimization: abandon pinned methods");
+ ThreadUtils.processItems(
+ this::forEachPinnedNonPrivateVirtualMethod,
+ this::abandonCallSitePropagationForMethodAndOverrides,
+ executorService);
+ timing.end();
+ }
+
+ private void forEachPinnedNonPrivateVirtualMethod(Consumer<ProgramMethod> consumer) {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
+ if (virtualProgramMethod.getDefinition().isNonPrivateVirtualMethod()
+ && appView.getKeepInfo().isPinned(virtualProgramMethod.getReference(), appView)) {
+ consumer.accept(virtualProgramMethod);
+ }
+ }
+ }
+ }
+
private void abandonCallSitePropagationForMethodAndOverrides(ProgramMethod method) {
Set<ProgramMethod> abandonSet = Sets.newIdentityHashSet();
if (method.getDefinition().isNonPrivateVirtualMethod()) {
@@ -467,12 +510,6 @@
return targetsToRevisit;
}
- @Override
- public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
- // Run IRConverter#optimize.
- return null;
- }
-
private synchronized boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
InvokeMethod invoke, ProgramMethod context) {
ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 7dae8cd..55780b6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.code.InvokeInterface;
import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -63,7 +64,7 @@
Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
DominatorTree dominatorTree = new DominatorTree(code);
Map<Value, Map<DexType, Value>> castedReceiverCache = new IdentityHashMap<>();
- Set<CheckCast> newCheckCastInstructions = Sets.newIdentityHashSet();
+ Set<SafeCheckCast> newCheckCastInstructions = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
@@ -261,7 +262,8 @@
castedReceiverCache.putIfAbsent(receiver, new IdentityHashMap<>());
castedReceiverCache.get(receiver).put(holderClass.getType(), newReceiver);
}
- CheckCast checkCast = new CheckCast(newReceiver, receiver, holderClass.getType());
+ SafeCheckCast checkCast =
+ new SafeCheckCast(newReceiver, receiver, holderClass.getType());
checkCast.setPosition(invoke.getPosition());
newCheckCastInstructions.add(checkCast);
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 a6f246f..9e82361 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
@@ -46,7 +46,6 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.CodeOptimization;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostOptimization;
@@ -71,7 +70,6 @@
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
@@ -244,12 +242,6 @@
return doubleInlineCallers;
}
- @Override
- public Collection<CodeOptimization> codeOptimizationsForPostProcessing() {
- // Run IRConverter#optimize.
- return null; // Technically same as return converter.getOptimizationForPostIRProcessing();
- }
-
/**
* Encodes the constraints for inlining a method's instructions into a different context.
* <p>
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index 07471e7..b47ec05 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.enums;
import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.google.common.collect.ImmutableMap;
@@ -30,6 +31,12 @@
return map.isEmpty();
}
+ public EnumData get(DexProgramClass enumClass) {
+ EnumData enumData = map.get(enumClass.getType());
+ assert enumData != null;
+ return enumData;
+ }
+
public Set<DexType> getUnboxedEnums() {
return map.keySet();
}
@@ -55,6 +62,16 @@
return map.get(enumType).getValuesSize();
}
+ public int getMaxValuesSize() {
+ int maxValuesSize = 0;
+ for (EnumData data : map.values()) {
+ if (data.hasValues()) {
+ maxValuesSize = Math.max(maxValuesSize, data.getValuesSize());
+ }
+ }
+ return maxValuesSize;
+ }
+
public boolean matchesValuesField(DexField staticField) {
assert map.containsKey(staticField.holder);
return map.get(staticField.holder).matchesValuesField(staticField);
@@ -101,8 +118,12 @@
return valuesFields.contains(field);
}
+ public boolean hasValues() {
+ return valuesSize != INVALID_VALUES_SIZE;
+ }
+
public int getValuesSize() {
- assert valuesSize != INVALID_VALUES_SIZE;
+ assert hasValues();
return valuesSize;
}
}
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 0b47a53..edd774e 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
@@ -38,7 +38,6 @@
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
@@ -69,6 +68,7 @@
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
@@ -127,10 +127,10 @@
// Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
// enum if the optimization eventually decides to unbox it.
private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
- private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
- ProgramPackageCollection.createEmpty();
private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
new ConcurrentHashMap<>();
+ private final ProgramMethodSet methodsDependingOnLibraryModelisation =
+ ProgramMethodSet.createConcurrent();
private final DexEncodedField ordinalField;
@@ -181,6 +181,10 @@
return false;
}
+ private void markMethodDependsOnLibraryModelisation(ProgramMethod method) {
+ methodsDependingOnLibraryModelisation.add(method);
+ }
+
private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
if (lattice.isClassType()) {
DexType classType = lattice.asClassType().getClassType();
@@ -199,7 +203,7 @@
return enumUnboxingCandidatesInfo.getCandidateClassOrNull(type);
}
- public void analyzeEnums(IRCode code) {
+ public void analyzeEnums(IRCode code, MutableMethodConversionOptions conversionOptions) {
Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
@@ -262,6 +266,9 @@
enumUnboxingCandidatesInfo.addMethodDependency(eligibleEnum, code.context());
}
}
+ if (methodsDependingOnLibraryModelisation.contains(code.context())) {
+ conversionOptions.disablePeepholeOptimizations();
+ }
}
private void analyzeFieldInstruction(FieldInstruction fieldInstruction, IRCode code) {
@@ -365,10 +372,12 @@
// The name data is required for the correct mapping from the enum name to the ordinal
// in the valueOf utility method.
addRequiredNameData(enumClass);
+ markMethodDependsOnLibraryModelisation(context);
continue;
}
if (singleTarget.getReference()
== factory.javaLangReflectArrayMembers.newInstanceMethodWithDimensions) {
+ markMethodDependsOnLibraryModelisation(context);
continue;
}
}
@@ -469,18 +478,22 @@
DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
FieldAccessInfoCollectionModifier.builder();
- UnboxedEnumMemberRelocator relocator =
- UnboxedEnumMemberRelocator.builder(appView)
+
+ EnumUnboxingUtilityClasses utilityClasses =
+ EnumUnboxingUtilityClasses.builder(appView)
.synthesizeEnumUnboxingUtilityClasses(
enumClassesToUnbox,
- enumsToUnboxWithPackageRequirement,
+ enumDataMap,
appBuilder,
fieldAccessInfoCollectionModifierBuilder)
.build();
+ utilityClasses.forEach(
+ utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
+
fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
- enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
+ enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
EnumUnboxingLens enumUnboxingLens =
- new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
+ new EnumUnboxingTreeFixer(appView, enumsToUnbox, utilityClasses, enumUnboxerRewriter)
.fixupTypeReferences();
enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
appView.setUnboxedEnums(enumDataMap);
@@ -488,6 +501,10 @@
appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
updateOptimizationInfos(executorService, feedback);
postBuilder.put(dependencies);
+ // Methods depending on library modelisation need to be reprocessed so they are peephole
+ // optimized.
+ postBuilder.put(methodsDependingOnLibraryModelisation);
+ methodsDependingOnLibraryModelisation.clear();
postBuilder.rewrittenWithLens(appView, previousLens);
}
@@ -523,7 +540,6 @@
public EnumDataMap finishAnalysis() {
analyzeInitializers();
- analyzeAccessibility();
EnumDataMap enumDataMap = analyzeEnumInstances();
if (debugLogEnabled) {
// Remove all enums that have been reported as being unboxable.
@@ -725,39 +741,6 @@
return OptionalInt.empty();
}
- private void analyzeAccessibility() {
- // Unboxing an enum will require to move its methods to a different class, which may impact
- // accessibility. For a quick analysis we simply reuse the inliner analysis.
- enumUnboxingCandidatesInfo.forEachCandidate(
- enumClass -> {
- Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
- if (classConstraint == Constraint.NEVER) {
- markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
- } else if (classConstraint == Constraint.PACKAGE) {
- enumsToUnboxWithPackageRequirement.addProgramClass(enumClass);
- }
- });
- }
-
- private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
- Constraint classConstraint = Constraint.ALWAYS;
- EnumAccessibilityUseRegistry useRegistry = null;
- for (DexEncodedMethod method : enumClass.methods()) {
- // Enum initializer are analyzed in analyzeInitializers instead.
- if (!method.isInitializer()) {
- if (useRegistry == null) {
- useRegistry = new EnumAccessibilityUseRegistry(factory);
- }
- Constraint methodConstraint = constraintForEnumUnboxing(method, useRegistry);
- classConstraint = classConstraint.meet(methodConstraint);
- if (classConstraint == Constraint.NEVER) {
- return classConstraint;
- }
- }
- }
- return classConstraint;
- }
-
public Constraint constraintForEnumUnboxing(
DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
return useRegistry.computeConstraint(method.asProgramMethod(appView));
@@ -1210,6 +1193,25 @@
assert targetHolder.isLibraryClass();
+ Reason reason =
+ analyzeLibraryInvoke(
+ invoke, code, context, enumClass, enumValue, singleTargetReference, targetHolder);
+
+ if (reason == Reason.ELIGIBLE) {
+ markMethodDependsOnLibraryModelisation(context);
+ }
+
+ return reason;
+ }
+
+ private Reason analyzeLibraryInvoke(
+ InvokeMethod invoke,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue,
+ DexMethod singleTargetReference,
+ DexClass targetHolder) {
if (targetHolder.getType() != factory.enumType) {
// System.identityHashCode(Object) is supported for proto enums.
// Object#getClass without outValue and Objects.requireNonNull are supported since R8
@@ -1221,6 +1223,11 @@
addRequiredNameData(enumClass);
return Reason.ELIGIBLE;
}
+ if (singleTargetReference == factory.stringBuilderMethods.appendObject
+ || singleTargetReference == factory.stringBufferMethods.appendObject) {
+ addRequiredNameData(enumClass);
+ return Reason.ELIGIBLE;
+ }
if (singleTargetReference == factory.objectMembers.getClass
&& (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers())) {
// This is a hidden null check.
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 7ddfbaa..3bbd636 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
@@ -4,10 +4,7 @@
package com.android.tools.r8.ir.optimize.enums;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -38,6 +35,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StaticGet;
@@ -46,12 +44,14 @@
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
@@ -69,64 +69,67 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
+ private final InternalOptions options;
private final EnumDataMap unboxedEnumsData;
- private final UnboxedEnumMemberRelocator relocator;
private EnumUnboxingLens enumUnboxingLens;
+ private final EnumUnboxingUtilityClasses utilityClasses;
private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
private final DexMethod ordinalUtilityMethod;
private final DexMethod equalsUtilityMethod;
private final DexMethod compareToUtilityMethod;
- private final DexMethod valuesUtilityMethod;
private final DexMethod zeroCheckMethod;
private final DexMethod zeroCheckMessageMethod;
EnumUnboxingRewriter(
AppView<AppInfoWithLiveness> appView,
EnumDataMap unboxedEnumsInstanceFieldData,
- UnboxedEnumMemberRelocator relocator) {
+ EnumUnboxingUtilityClasses utilityClasses) {
this.appView = appView;
this.factory = appView.dexItemFactory();
+ this.options = appView.options();
this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
- this.relocator = relocator;
+ this.utilityClasses = utilityClasses;
// Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
- DexType defaultEnumUnboxingUtility = relocator.getDefaultEnumUnboxingUtility();
+ DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
this.ordinalUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
this.equalsUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.booleanType, factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
this.compareToUtilityMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.intType, factory.intType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
- // Custom methods for generated field $VALUES initialization.
- this.valuesUtilityMethod =
- factory.createMethod(
- defaultEnumUnboxingUtility,
- factory.createProto(factory.intArrayType, factory.intType),
- ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "values");
// Custom methods for Object#getClass without outValue and Objects.requireNonNull.
this.zeroCheckMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.voidType, factory.intType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
this.zeroCheckMessageMethod =
factory.createMethod(
- defaultEnumUnboxingUtility,
+ sharedEnumUnboxingUtilityType,
factory.createProto(factory.voidType, factory.intType, factory.stringType),
ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
}
+ private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
+ return utilityClasses.getLocalUtilityClass(enumType);
+ }
+
+ private SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
+ return utilityClasses.getSharedUtilityClass();
+ }
+
public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
this.enumUnboxingLens = enumUnboxingLens;
}
@@ -142,13 +145,21 @@
Map<Instruction, DexType> convertedEnums = new IdentityHashMap<>();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
+ Set<BasicBlock> seenBlocks = Sets.newIdentityHashSet();
+ Set<Instruction> instructionsToRemove = Sets.newIdentityHashSet();
Value zeroConstValue = null;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
+ seenBlocks.add(block);
zeroConstValue = fixNullsInBlockPhis(code, block, zeroConstValue);
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
+ if (instructionsToRemove.contains(instruction)) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+
// Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
// counterpart. The rewriting (== or match) is based on the following:
// - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
@@ -159,8 +170,8 @@
if (instruction.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (enumType != null) {
- DexMethod invokedMethod = invokeMethod.getInvokedMethod();
if (invokedMethod == factory.enumMembers.ordinalMethod
|| invokedMethod.match(factory.enumMembers.hashCode)) {
replaceEnumInvoke(
@@ -190,6 +201,42 @@
assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
replaceEnumInvoke(
iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+ continue;
+ }
+ } else if (invokedMethod == factory.stringBuilderMethods.appendObject
+ || invokedMethod == factory.stringBufferMethods.appendObject) {
+ // Rewrites stringBuilder.append(enumInstance) as if it was
+ // stringBuilder.append(String.valueOf(unboxedEnumInstance));
+ Value enumArg = invokeMethod.getArgument(1);
+ DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
+ if (enumArgType != null) {
+ DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
+ InvokeStatic toStringInvoke =
+ InvokeStatic.builder()
+ .setMethod(stringValueOfMethod)
+ .setSingleArgument(enumArg)
+ .setFreshOutValue(appView, code)
+ .setPosition(invokeMethod)
+ .build();
+ DexMethod newAppendMethod =
+ invokedMethod == factory.stringBuilderMethods.appendObject
+ ? factory.stringBuilderMethods.appendString
+ : factory.stringBufferMethods.appendString;
+ List<Value> arguments =
+ ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
+ InvokeVirtual invokeAppendString =
+ new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
+ invokeAppendString.setPosition(invokeMethod.getPosition());
+ iterator.replaceCurrentInstruction(toStringInvoke);
+ if (block.hasCatchHandlers()) {
+ iterator
+ .splitCopyCatchHandlers(code, blocks, appView.options())
+ .listIterator(code)
+ .add(invokeAppendString);
+ } else {
+ iterator.add(invokeAppendString);
+ }
+ continue;
}
}
} else if (instruction.isInvokeStatic()) {
@@ -264,38 +311,46 @@
StaticGet staticGet = instruction.asStaticGet();
DexField field = staticGet.getField();
DexType holder = field.holder;
- if (unboxedEnumsData.isUnboxedEnum(holder)) {
- if (staticGet.outValue() == null) {
- iterator.removeOrReplaceByDebugLocalRead();
- continue;
- }
- affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
- if (unboxedEnumsData.matchesValuesField(field)) {
- utilityMethods.computeIfAbsent(
- valuesUtilityMethod, m -> synthesizeValuesUtilityMethod());
- DexField fieldValues = createValuesField(holder);
- DexMethod methodValues = createValuesMethod(holder);
- utilityMethods.computeIfAbsent(
- methodValues,
- m ->
- computeValuesEncodedMethod(
- m, fieldValues, unboxedEnumsData.getValuesSize(holder)));
- Value rewrittenOutValue =
- code.createValue(
- ArrayTypeElement.create(TypeElement.getInt(), definitelyNotNull()));
- InvokeStatic invoke =
- new InvokeStatic(methodValues, rewrittenOutValue, ImmutableList.of());
- iterator.replaceCurrentInstruction(invoke);
- convertedEnums.put(invoke, holder);
- } else {
- // Replace by ordinal + 1 for null check (null is 0).
- assert unboxedEnumsData.hasUnboxedValueFor(field)
- : "Invalid read to " + field.name + ", error during enum analysis";
- ConstNumber intConstant =
- code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
- iterator.replaceCurrentInstruction(intConstant);
- convertedEnums.put(intConstant, holder);
- }
+ if (!unboxedEnumsData.isUnboxedEnum(holder)) {
+ continue;
+ }
+ if (staticGet.hasUnusedOutValue()) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+ affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+ if (unboxedEnumsData.matchesValuesField(field)) {
+ // Load the size of this enum's $VALUES array before the current instruction.
+ iterator.previous();
+ Value sizeValue =
+ iterator.insertConstIntInstruction(
+ code, options, unboxedEnumsData.getValuesSize(holder));
+ iterator.next();
+
+ // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
+ InvokeStatic invoke =
+ InvokeStatic.builder()
+ .setMethod(getSharedUtilityClass().getValuesMethod())
+ .setFreshOutValue(appView, code)
+ .setSingleArgument(sizeValue)
+ .build();
+ iterator.replaceCurrentInstruction(invoke);
+
+ convertedEnums.put(invoke, holder);
+
+ // Check if the call to SharedUtilityClass.values(size) is followed by a call to
+ // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
+ // array. This is needed because the javac generated implementation of MyEnum.values()
+ // is implemented as `return $VALUES.clone()`.
+ removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
+ } else {
+ // Replace by ordinal + 1 for null check (null is 0).
+ assert unboxedEnumsData.hasUnboxedValueFor(field)
+ : "Invalid read to " + field.name + ", error during enum analysis";
+ ConstNumber intConstant =
+ code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
+ iterator.replaceCurrentInstruction(intConstant);
+ convertedEnums.put(intConstant, holder);
}
}
@@ -338,6 +393,26 @@
return affectedPhis;
}
+ private void removeRedundantValuesArrayCloning(
+ InvokeStatic invoke, Set<Instruction> instructionsToRemove, Set<BasicBlock> seenBlocks) {
+ for (Instruction user : invoke.outValue().aliasedUsers()) {
+ if (user.isInvokeVirtual()) {
+ InvokeVirtual cloneCandidate = user.asInvokeVirtual();
+ if (cloneCandidate.getInvokedMethod().match(appView.dexItemFactory().objectMembers.clone)) {
+ if (cloneCandidate.hasOutValue()) {
+ cloneCandidate.outValue().replaceUsers(invoke.outValue());
+ }
+ BasicBlock cloneBlock = cloneCandidate.getBlock();
+ if (cloneBlock == invoke.getBlock() || !seenBlocks.contains(cloneBlock)) {
+ instructionsToRemove.add(cloneCandidate);
+ } else {
+ cloneBlock.removeInstruction(cloneCandidate);
+ }
+ }
+ }
+ }
+ }
+
private void rewriteNameMethod(
InstructionListIterator iterator, InvokeMethodWithReceiver invokeMethod, DexType enumType) {
DexMethod toStringMethod =
@@ -368,7 +443,7 @@
while (iterator.hasNext() && iterator.peekNext().isArgument()) {
iterator.next();
}
- return iterator.insertConstNumberInstruction(code, appView.options(), 0, TypeElement.getInt());
+ return iterator.insertConstIntInstruction(code, options, 0);
}
private DexMethod computeInstanceFieldMethod(DexField field) {
@@ -427,34 +502,6 @@
return type.toSourceString().replace('.', '$');
}
- private DexField createValuesField(DexType enumType) {
- return createValuesField(enumType, relocator.getNewMemberLocationFor(enumType), factory);
- }
-
- static DexField createValuesField(
- DexType enumType, DexType enumUtilityClass, DexItemFactory dexItemFactory) {
- return dexItemFactory.createField(
- enumUtilityClass,
- dexItemFactory.intArrayType,
- "$$values$$field$" + compatibleName(enumType));
- }
-
- private DexMethod createValuesMethod(DexType enumType) {
- return factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
- factory.createProto(factory.intArrayType),
- "$$values$$method$" + compatibleName(enumType));
- }
-
- private DexEncodedMethod computeValuesEncodedMethod(
- DexMethod method, DexField fieldValues, int numEnumInstances) {
- CfCode cfCode =
- new EnumUnboxingCfCodeProvider.EnumUnboxingValuesCfCodeProvider(
- appView, method.holder, fieldValues, numEnumInstances, valuesUtilityMethod)
- .generateCfCode();
- return synthesizeUtilityMethod(cfCode, method, true);
- }
-
private DexMethod computeInstanceFieldUtilityMethod(DexType enumType, DexField field) {
assert unboxedEnumsData.isUnboxedEnum(enumType);
assert field.holder == enumType || field.holder == factory.enumType;
@@ -466,7 +513,7 @@
+ compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(field.type, factory.intType),
methodName);
utilityMethods.computeIfAbsent(
@@ -480,7 +527,7 @@
String methodName = "string$valueOf$" + compatibleName(enumType);
DexMethod fieldMethod =
factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(factory.stringType, factory.intType),
methodName);
AbstractValue nullString =
@@ -495,7 +542,7 @@
assert unboxedEnumsData.isUnboxedEnum(enumType);
DexMethod valueOf =
factory.createMethod(
- relocator.getNewMemberLocationFor(enumType),
+ utilityClasses.getLocalUtilityClass(enumType).getType(),
factory.createProto(factory.intType, factory.stringType),
"valueOf" + compatibleName(enumType));
utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
@@ -555,7 +602,7 @@
}
// We make the order deterministic.
for (List<T> value : encodedMembersMap.values()) {
- value.sort((m1, m2) -> m1.getReference().compareTo(m2.getReference()));
+ value.sort(Comparator.comparing(DexEncodedMember::getReference));
}
return encodedMembersMap;
}
@@ -572,7 +619,7 @@
unboxedEnumsData.getInstanceFieldData(enumType, field).asEnumFieldMappingData(),
nullValue)
.generateCfCode();
- return synthesizeUtilityMethod(cfCode, method, false);
+ return synthesizeUtilityMethod(cfCode, method);
}
private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
@@ -589,51 +636,45 @@
.getInstanceFieldData(enumType, factory.enumMembers.nameField)
.asEnumFieldMappingData())
.generateCfCode();
- return synthesizeUtilityMethod(cfCode, method, false);
+ return synthesizeUtilityMethod(cfCode, method);
}
private DexEncodedMethod synthesizeZeroCheckMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
- return synthesizeUtilityMethod(cfCode, zeroCheckMethod, false);
+ return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
}
private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
appView.options(), zeroCheckMessageMethod);
- return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod, false);
+ return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
}
private DexEncodedMethod synthesizeOrdinalMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
- return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod, false);
+ return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
}
private DexEncodedMethod synthesizeEqualsMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
- return synthesizeUtilityMethod(cfCode, equalsUtilityMethod, false);
+ return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
}
private DexEncodedMethod synthesizeCompareToMethod() {
CfCode cfCode =
EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
appView.options(), compareToUtilityMethod);
- return synthesizeUtilityMethod(cfCode, compareToUtilityMethod, false);
+ return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
}
- private DexEncodedMethod synthesizeValuesUtilityMethod() {
- CfCode cfCode =
- EnumUnboxingCfMethods.EnumUnboxingMethods_values(appView.options(), valuesUtilityMethod);
- return synthesizeUtilityMethod(cfCode, valuesUtilityMethod, false);
- }
-
- private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method, boolean sync) {
+ private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
return new DexEncodedMethod(
method,
- synthesizedMethodAccessFlags(sync),
+ MethodAccessFlags.createPublicStaticSynthetic(),
MethodTypeSignature.noSignature(),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
@@ -641,12 +682,4 @@
true,
REQUIRED_CLASS_FILE_VERSION);
}
-
- private MethodAccessFlags synthesizedMethodAccessFlags(boolean sync) {
- int access = Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC | Constants.ACC_STATIC;
- if (sync) {
- access = access | Constants.ACC_SYNCHRONIZED;
- }
- return MethodAccessFlags.fromSharedAccessFlags(access, false);
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index a29e290..8bab979 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -38,18 +38,18 @@
private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory factory;
private final Set<DexType> enumsToUnbox;
- private final UnboxedEnumMemberRelocator relocator;
+ private final EnumUnboxingUtilityClasses utilityClasses;
private final EnumUnboxingRewriter enumUnboxerRewriter;
EnumUnboxingTreeFixer(
AppView<AppInfoWithLiveness> appView,
Set<DexType> enumsToUnbox,
- UnboxedEnumMemberRelocator relocator,
+ EnumUnboxingUtilityClasses utilityClasses,
EnumUnboxingRewriter enumUnboxerRewriter) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.enumsToUnbox = enumsToUnbox;
- this.relocator = relocator;
+ this.utilityClasses = utilityClasses;
this.enumUnboxerRewriter = enumUnboxerRewriter;
}
@@ -57,7 +57,7 @@
assert enumUnboxerRewriter != null;
// Fix all methods and fields using enums to unbox.
for (DexProgramClass clazz : appView.appInfo().classes()) {
- if (enumsToUnbox.contains(clazz.type)) {
+ if (enumsToUnbox.contains(clazz.getType())) {
// Clear the initializers and move the static methods to the new location.
Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
clazz
@@ -67,7 +67,7 @@
if (m.isInitializer()) {
clearEnumToUnboxMethod(m);
} else {
- DexType newHolder = relocator.getNewMemberLocationFor(clazz.type);
+ DexType newHolder = utilityClasses.getLocalUtilityClass(clazz).getType();
List<DexEncodedMethod> movedMethods =
unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
@@ -178,16 +178,15 @@
newMethod =
factory.createInstanceInitializerWithFreshProto(
newMethod,
- relocator.getDefaultEnumUnboxingUtility(),
+ utilityClasses.getSharedUtilityClass().getType(),
tryMethod -> holder.lookupMethod(tryMethod) == null);
} else {
int index = 0;
while (holder.lookupMethod(newMethod) != null) {
newMethod =
- factory.createMethod(
- newMethod.holder,
- newMethod.proto,
- encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+ newMethod.withName(
+ encodedMethod.getName().toString() + "$enumunboxing$" + index++,
+ appView.dexItemFactory());
}
}
return newMethod;
@@ -202,7 +201,7 @@
DexField field = encodedField.getReference();
DexType newType = fixupType(field.type);
if (newType != field.type) {
- DexField newField = factory.createField(field.holder, newType, field.name);
+ DexField newField = field.withType(newType, factory);
lensBuilder.move(field, newField);
DexEncodedField newEncodedField =
encodedField.toTypeSubstitutedField(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
new file mode 100644
index 0000000..6026c71
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClass.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, 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.enums;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public abstract class EnumUnboxingUtilityClass {
+
+ public abstract DexProgramClass getDefinition();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
new file mode 100644
index 0000000..1753375
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -0,0 +1,108 @@
+// 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.enums;
+
+import com.android.tools.r8.graph.AppView;
+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.DirectMappedDexApplication;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class EnumUnboxingUtilityClasses {
+
+ // Synthetic classes for utilities specific to the unboxing of a single enum.
+ private final ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
+
+ // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
+ // methods (ordinal(I), equals(II), etc.).
+ private final SharedEnumUnboxingUtilityClass sharedUtilityClass;
+
+ private EnumUnboxingUtilityClasses(
+ SharedEnumUnboxingUtilityClass sharedUtilityClass,
+ ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses) {
+ this.sharedUtilityClass = sharedUtilityClass;
+ this.localUtilityClasses = localUtilityClasses;
+ }
+
+ public void forEach(Consumer<? super EnumUnboxingUtilityClass> consumer) {
+ localUtilityClasses.values().forEach(consumer);
+ consumer.accept(getSharedUtilityClass());
+ }
+
+ public LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexProgramClass enumClass) {
+ return getLocalUtilityClass(enumClass.getType());
+ }
+
+ public LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
+ LocalEnumUnboxingUtilityClass localEnumUnboxingUtilityClass = localUtilityClasses.get(enumType);
+ assert localEnumUnboxingUtilityClass != null;
+ return localEnumUnboxingUtilityClass;
+ }
+
+ public SharedEnumUnboxingUtilityClass getSharedUtilityClass() {
+ return sharedUtilityClass;
+ }
+
+ public static Builder builder(AppView<AppInfoWithLiveness> appView) {
+ return new Builder(appView);
+ }
+
+ public static class Builder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses;
+ private SharedEnumUnboxingUtilityClass sharedUtilityClass;
+
+ public Builder(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ public Builder synthesizeEnumUnboxingUtilityClasses(
+ Set<DexProgramClass> enumsToUnbox,
+ EnumDataMap enumDataMap,
+ DirectMappedDexApplication.Builder appBuilder,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ SharedEnumUnboxingUtilityClass sharedUtilityClass =
+ SharedEnumUnboxingUtilityClass.builder(
+ appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
+ .build(appBuilder);
+ ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
+ createLocalUtilityClasses(enumsToUnbox, appBuilder);
+ this.localUtilityClasses = localUtilityClasses;
+ this.sharedUtilityClass = sharedUtilityClass;
+ return this;
+ }
+
+ public EnumUnboxingUtilityClasses build() {
+ return new EnumUnboxingUtilityClasses(sharedUtilityClass, localUtilityClasses);
+ }
+
+ private ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> createLocalUtilityClasses(
+ Set<DexProgramClass> enumsToUnbox, DirectMappedDexApplication.Builder appBuilder) {
+ ImmutableMap.Builder<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
+ ImmutableMap.builder();
+ for (DexProgramClass enumToUnbox : enumsToUnbox) {
+ localUtilityClasses.put(
+ enumToUnbox.getType(),
+ LocalEnumUnboxingUtilityClass.builder(appView, enumToUnbox).build(appBuilder));
+ }
+ return localUtilityClasses.build();
+ }
+
+ static DexType getUtilityClassType(
+ DexProgramClass context, String suffix, DexItemFactory dexItemFactory) {
+ return dexItemFactory.createType(
+ DescriptorUtils.getDescriptorFromClassBinaryName(
+ DescriptorUtils.getBinaryNameFromDescriptor(context.getType().toDescriptorString())
+ + suffix));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
new file mode 100644
index 0000000..f4d4636
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -0,0 +1,94 @@
+// 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.enums;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
+
+public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
+
+ private static final String ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX =
+ "$r8$EnumUnboxingLocalUtility";
+
+ private final DexProgramClass localUtilityClass;
+
+ public LocalEnumUnboxingUtilityClass(DexProgramClass localUtilityClass) {
+ this.localUtilityClass = localUtilityClass;
+ }
+
+ public static Builder builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+ return new Builder(appView, enumToUnbox);
+ }
+
+ @Override
+ public DexProgramClass getDefinition() {
+ return localUtilityClass;
+ }
+
+ public DexType getType() {
+ return localUtilityClass.getType();
+ }
+
+ public static class Builder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+ private final DexProgramClass enumToUnbox;
+ private final DexType localUtilityClassType;
+
+ private Builder(AppView<AppInfoWithLiveness> appView, DexProgramClass enumToUnbox) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.enumToUnbox = enumToUnbox;
+ this.localUtilityClassType =
+ EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
+ enumToUnbox, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX, dexItemFactory);
+
+ assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
+ }
+
+ LocalEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+ DexProgramClass clazz = createClass();
+ appBuilder.addSynthesizedClass(clazz);
+ appView.appInfo().addSynthesizedClass(clazz, enumToUnbox);
+ return new LocalEnumUnboxingUtilityClass(clazz);
+ }
+
+ private DexProgramClass createClass() {
+ return new DexProgramClass(
+ localUtilityClassType,
+ null,
+ new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+ ClassAccessFlags.createPublicFinalSynthetic(),
+ appView.dexItemFactory().objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ ClassSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedField.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ appView.dexItemFactory().getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
new file mode 100644
index 0000000..19adc9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -0,0 +1,279 @@
+// 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.enums;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+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.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.objectweb.asm.Opcodes;
+
+public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
+
+ public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
+ "$r8$EnumUnboxingSharedUtility";
+
+ private final DexProgramClass sharedUtilityClass;
+ private final ProgramField valuesField;
+ private final ProgramMethod valuesMethod;
+
+ public SharedEnumUnboxingUtilityClass(
+ DexProgramClass sharedUtilityClass, ProgramField valuesField, ProgramMethod valuesMethod) {
+ this.sharedUtilityClass = sharedUtilityClass;
+ this.valuesField = valuesField;
+ this.valuesMethod = valuesMethod;
+ }
+
+ public static Builder builder(
+ AppView<AppInfoWithLiveness> appView,
+ EnumDataMap enumDataMap,
+ Set<DexProgramClass> enumsToUnbox,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ return new Builder(
+ appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
+ }
+
+ @Override
+ public DexProgramClass getDefinition() {
+ return sharedUtilityClass;
+ }
+
+ public ProgramField getValuesField() {
+ return valuesField;
+ }
+
+ public ProgramMethod getValuesMethod() {
+ return valuesMethod;
+ }
+
+ public DexType getType() {
+ return sharedUtilityClass.getType();
+ }
+
+ public static class Builder {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final DexItemFactory dexItemFactory;
+ private final EnumDataMap enumDataMap;
+ private final Set<DexProgramClass> enumsToUnbox;
+ private final FieldAccessInfoCollectionModifier.Builder
+ fieldAccessInfoCollectionModifierBuilder;
+ private final DexType sharedUtilityClassType;
+
+ private DexEncodedField valuesField;
+ private DexEncodedMethod valuesMethod;
+
+ private Builder(
+ AppView<AppInfoWithLiveness> appView,
+ EnumDataMap enumDataMap,
+ Set<DexProgramClass> enumsToUnbox,
+ FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.enumDataMap = enumDataMap;
+ this.enumsToUnbox = enumsToUnbox;
+ this.fieldAccessInfoCollectionModifierBuilder = fieldAccessInfoCollectionModifierBuilder;
+ this.sharedUtilityClassType =
+ EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
+ findDeterministicContextType(enumsToUnbox),
+ ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX,
+ dexItemFactory);
+
+ assert appView.appInfo().definitionForWithoutExistenceAssert(sharedUtilityClassType) == null;
+ }
+
+ SharedEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+ DexProgramClass clazz = createClass();
+ appBuilder.addSynthesizedClass(clazz);
+ appView.appInfo().addSynthesizedClassToBase(clazz, enumsToUnbox);
+ return new SharedEnumUnboxingUtilityClass(
+ clazz, new ProgramField(clazz, valuesField), new ProgramMethod(clazz, valuesMethod));
+ }
+
+ private DexProgramClass createClass() {
+ DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
+ return new DexProgramClass(
+ sharedUtilityClassType,
+ null,
+ new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+ ClassAccessFlags.createPublicFinalSynthetic(),
+ dexItemFactory.objectType,
+ DexTypeList.empty(),
+ null,
+ null,
+ Collections.emptyList(),
+ null,
+ Collections.emptyList(),
+ ClassSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ new DexEncodedField[] {valuesField},
+ DexEncodedField.EMPTY_ARRAY,
+ new DexEncodedMethod[] {
+ createClassInitializer(valuesField), createValuesMethod(valuesField)
+ },
+ DexEncodedMethod.EMPTY_ARRAY,
+ dexItemFactory.getSkipNameValidationForTesting(),
+ DexProgramClass::checksumFromType);
+ }
+
+ // Fields.
+
+ private DexEncodedField createValuesField(DexType sharedUtilityClassType) {
+ DexEncodedField valuesField =
+ new DexEncodedField(
+ dexItemFactory.createField(
+ sharedUtilityClassType, dexItemFactory.intArrayType, "$VALUES"),
+ FieldAccessFlags.createPublicStaticFinalSynthetic(),
+ FieldTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ DexEncodedField.NO_STATIC_VALUE,
+ DexEncodedField.NOT_DEPRECATED,
+ DexEncodedField.D8_R8_SYNTHESIZED);
+ fieldAccessInfoCollectionModifierBuilder
+ .recordFieldReadInUnknownContext(valuesField.getReference())
+ .recordFieldWriteInUnknownContext(valuesField.getReference());
+ this.valuesField = valuesField;
+ return valuesField;
+ }
+
+ // Methods.
+
+ private DexEncodedMethod createClassInitializer(DexEncodedField valuesField) {
+ return new DexEncodedMethod(
+ dexItemFactory.createClassInitializer(sharedUtilityClassType),
+ MethodAccessFlags.createForClassInitializer(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ createClassInitializerCode(valuesField),
+ DexEncodedMethod.D8_R8_SYNTHESIZED,
+ CfVersion.V1_6);
+ }
+
+ private CfCode createClassInitializerCode(DexEncodedField valuesField) {
+ int maxValuesArraySize = enumDataMap.getMaxValuesSize();
+ int numberOfInstructions = 4 + maxValuesArraySize * 4;
+ List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
+ instructions.add(new CfConstNumber(maxValuesArraySize, ValueType.INT));
+ instructions.add(new CfNewArray(dexItemFactory.intArrayType));
+ for (int i = 0; i < maxValuesArraySize; i++) {
+ instructions.add(new CfStackInstruction(Opcode.Dup));
+ instructions.add(new CfConstNumber(i, ValueType.INT));
+ // i + 1 because 0 represents the null value.
+ instructions.add(new CfConstNumber(i + 1, ValueType.INT));
+ instructions.add(new CfArrayStore(MemberType.INT));
+ }
+ instructions.add(new CfFieldInstruction(Opcodes.PUTSTATIC, valuesField.getReference()));
+ instructions.add(new CfReturnVoid());
+
+ int maxStack = 4;
+ int maxLocals = 0;
+ return new CfCode(
+ sharedUtilityClassType,
+ maxStack,
+ maxLocals,
+ instructions,
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ private DexEncodedMethod createValuesMethod(DexEncodedField valuesField) {
+ DexEncodedMethod valuesMethod =
+ new DexEncodedMethod(
+ dexItemFactory.createMethod(
+ sharedUtilityClassType,
+ dexItemFactory.createProto(dexItemFactory.intArrayType, dexItemFactory.intType),
+ "values"),
+ MethodAccessFlags.createPublicStaticSynthetic(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ createValuesMethodCode(valuesField),
+ DexEncodedMethod.D8_R8_SYNTHESIZED,
+ CfVersion.V1_6);
+ this.valuesMethod = valuesMethod;
+ return valuesMethod;
+ }
+
+ private CfCode createValuesMethodCode(DexEncodedField valuesField) {
+ int maxStack = 5;
+ int maxLocals = 2;
+ int argumentLocalSlot = 0;
+ int resultLocalSlot = 1;
+ return new CfCode(
+ sharedUtilityClassType,
+ maxStack,
+ maxLocals,
+ ImmutableList.of(
+ // int[] result = new int[size];
+ new CfLoad(ValueType.INT, argumentLocalSlot),
+ new CfNewArray(dexItemFactory.intArrayType),
+ new CfStore(ValueType.OBJECT, resultLocalSlot),
+ // System.arraycopy(SharedUtilityClass.$VALUES, 0, result, 0, size);
+ new CfFieldInstruction(Opcodes.GETSTATIC, valuesField.getReference()),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.OBJECT, resultLocalSlot),
+ new CfConstNumber(0, ValueType.INT),
+ new CfLoad(ValueType.INT, argumentLocalSlot),
+ new CfInvoke(
+ Opcodes.INVOKESTATIC, dexItemFactory.javaLangSystemMethods.arraycopy, false),
+ // return result
+ new CfLoad(ValueType.OBJECT, resultLocalSlot),
+ new CfReturn(ValueType.OBJECT)),
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ private static DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
+ DexProgramClass deterministicContext = null;
+ for (DexProgramClass context : contexts) {
+ if (deterministicContext == null) {
+ deterministicContext = context;
+ } else if (context.type.compareTo(deterministicContext.type) < 0) {
+ deterministicContext = context;
+ }
+ }
+ return deterministicContext;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
deleted file mode 100644
index 54c416a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ /dev/null
@@ -1,198 +0,0 @@
-// 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.enums;
-
-import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-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.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.ProgramPackage;
-import com.android.tools.r8.graph.ProgramPackageCollection;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableMap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class UnboxedEnumMemberRelocator {
-
- public static final String ENUM_UNBOXING_UTILITY_CLASS_SUFFIX = "$r8$EnumUnboxingUtility";
-
- // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
- // methods (ordinal(I), equals(II), etc.) and the unboxed enums members which were free to be
- // placed anywhere.
- private final DexType defaultEnumUnboxingUtility;
- // Some unboxed enum members have to be placed in a specific package, in this case, we keep a
- // map from unboxed enum types to synthetic classes, so that all members of unboxed enums in the
- // keys are moved to the corresponding value.
- private final ImmutableMap<DexType, DexType> relocationMap;
-
- public DexType getDefaultEnumUnboxingUtility() {
- return defaultEnumUnboxingUtility;
- }
-
- public DexType getNewMemberLocationFor(DexType enumType) {
- return relocationMap.getOrDefault(enumType, defaultEnumUnboxingUtility);
- }
-
- private UnboxedEnumMemberRelocator(
- DexType defaultEnumUnboxingUtility, ImmutableMap<DexType, DexType> relocationMap) {
- this.defaultEnumUnboxingUtility = defaultEnumUnboxingUtility;
- this.relocationMap = relocationMap;
- }
-
- public static Builder builder(AppView<?> appView) {
- return new Builder(appView);
- }
-
- public static class Builder {
- private DexProgramClass defaultEnumUnboxingUtility;
- private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
- private final AppView<?> appView;
-
- public Builder(AppView<?> appView) {
- this.appView = appView;
- }
-
- public Builder synthesizeEnumUnboxingUtilityClasses(
- Set<DexProgramClass> enumsToUnbox,
- ProgramPackageCollection enumsToUnboxWithPackageRequirement,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- Set<DexProgramClass> enumsToUnboxWithoutPackageRequirement =
- SetUtils.newIdentityHashSet(enumsToUnbox);
- enumsToUnboxWithoutPackageRequirement.removeIf(enumsToUnboxWithPackageRequirement::contains);
- defaultEnumUnboxingUtility =
- synthesizeUtilityClass(
- enumsToUnbox,
- enumsToUnboxWithoutPackageRequirement,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder);
- if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
- synthesizeRelocationMap(
- enumsToUnbox,
- enumsToUnboxWithPackageRequirement,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder);
- }
- return this;
- }
-
- public UnboxedEnumMemberRelocator build() {
- return new UnboxedEnumMemberRelocator(
- defaultEnumUnboxingUtility.getType(), ImmutableMap.copyOf(relocationMap));
- }
-
- private void synthesizeRelocationMap(
- Set<DexProgramClass> contexts,
- ProgramPackageCollection enumsToUnboxWithPackageRequirement,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
- Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
- DexProgramClass enumUtilityClass =
- synthesizeUtilityClass(
- contexts,
- enumsToUnboxInPackage,
- appBuilder,
- fieldAccessInfoCollectionModifierBuilder);
- if (enumUtilityClass != defaultEnumUnboxingUtility) {
- for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
- assert !relocationMap.containsKey(enumToUnbox.type);
- relocationMap.put(enumToUnbox.type, enumUtilityClass.getType());
- }
- }
- }
- }
-
- private DexProgramClass synthesizeUtilityClass(
- Set<DexProgramClass> contexts,
- Set<DexProgramClass> relocatedEnums,
- DirectMappedDexApplication.Builder appBuilder,
- FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
- DexProgramClass deterministicContext = findDeterministicContextType(contexts);
- String descriptorString = deterministicContext.getType().toDescriptorString();
- String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
- String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
- DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
-
- // Required fields.
- List<DexEncodedField> staticFields = new ArrayList<>(relocatedEnums.size());
- for (DexProgramClass relocatedEnum : relocatedEnums) {
- DexField reference =
- createValuesField(relocatedEnum.getType(), type, appView.dexItemFactory());
- staticFields.add(
- new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic()));
- fieldAccessInfoCollectionModifierBuilder
- .recordFieldReadInUnknownContext(reference)
- .recordFieldWriteInUnknownContext(reference);
- }
- staticFields.sort(Comparator.comparing(DexEncodedField::getReference));
-
- // The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
- // depend on a subset of the unboxable enums, the deterministicContextType can therefore
- // be found twice, and in that case the same utility class can be used for both.
- if (defaultEnumUnboxingUtility != null && type == defaultEnumUnboxingUtility.getType()) {
- defaultEnumUnboxingUtility.appendStaticFields(staticFields);
- return defaultEnumUnboxingUtility;
- }
- assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
- DexProgramClass syntheticClass =
- new DexProgramClass(
- type,
- null,
- new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
- ClassAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
- appView.dexItemFactory().objectType,
- DexTypeList.empty(),
- null,
- null,
- Collections.emptyList(),
- null,
- Collections.emptyList(),
- ClassSignature.noSignature(),
- DexAnnotationSet.empty(),
- staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
- DexEncodedField.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- DexEncodedMethod.EMPTY_ARRAY,
- appView.dexItemFactory().getSkipNameValidationForTesting(),
- DexProgramClass::checksumFromType);
- appBuilder.addSynthesizedClass(syntheticClass);
- appView.appInfo().addSynthesizedClassToBase(syntheticClass, contexts);
- return syntheticClass;
- }
-
- private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
- DexProgramClass deterministicContext = null;
- for (DexProgramClass context : contexts) {
- if (deterministicContext == null) {
- deterministicContext = context;
- } else if (context.type.compareTo(deterministicContext.type) < 0) {
- deterministicContext = context;
- }
- }
- return deterministicContext;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 0408418..533174b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -8,12 +8,13 @@
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;
+import com.android.tools.r8.utils.AndroidApiLevel;
public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
private static final DefaultFieldOptimizationInfo INSTANCE = new DefaultFieldOptimizationInfo();
- private DefaultFieldOptimizationInfo() {}
+ protected DefaultFieldOptimizationInfo() {}
public static DefaultFieldOptimizationInfo getInstance() {
return INSTANCE;
@@ -55,6 +56,11 @@
}
@Override
+ public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+ throw new RuntimeException("Should never be called");
+ }
+
+ @Override
public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
return new MutableFieldOptimizationInfo();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java
new file mode 100644
index 0000000..27c6f43
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationWithMinApiInfo.java
@@ -0,0 +1,35 @@
+// 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;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class DefaultFieldOptimizationWithMinApiInfo extends DefaultFieldOptimizationInfo {
+
+ private static final DefaultFieldOptimizationWithMinApiInfo INSTANCE =
+ new DefaultFieldOptimizationWithMinApiInfo();
+
+ public static DefaultFieldOptimizationWithMinApiInfo getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean hasApiReferenceLevel() {
+ return true;
+ }
+
+ @Override
+ public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+ return minApi;
+ }
+
+ @Override
+ public MutableFieldOptimizationInfo toMutableOptimizationInfo() {
+ MutableFieldOptimizationInfo updatableFieldOptimizationInfo = super.toMutableOptimizationInfo();
+ // Use null to specify that the min api is set to minApi.
+ updatableFieldOptimizationInfo.setMinApiReferenceLevel();
+ return updatableFieldOptimizationInfo;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 1df189a..0aed133 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -180,7 +180,7 @@
@Override
public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
- return UNKNOWN_API_REFERENCE_LEVEL;
+ throw new RuntimeException("Should never be called");
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
index a5e21d9..0638369 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
@@ -30,7 +30,7 @@
MutableMethodOptimizationInfo updatableMethodOptimizationInfo =
super.toMutableOptimizationInfo();
// Use null to specify that the min api is set to minApi.
- updatableMethodOptimizationInfo.setApiReferenceLevel(null);
+ updatableMethodOptimizationInfo.setMinApiReferenceLevel();
return updatableMethodOptimizationInfo;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
index e9ce890..910669d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MemberOptimizationInfo.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.info;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
public interface MemberOptimizationInfo<
T extends MemberOptimizationInfo<T> & MutableOptimizationInfo> {
@@ -19,5 +21,11 @@
return null;
}
+ default boolean hasApiReferenceLevel() {
+ return false;
+ }
+
+ AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi);
+
T toMutableOptimizationInfo();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 41c145f..748d82d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import java.util.BitSet;
@@ -95,10 +94,6 @@
public abstract boolean returnValueHasBeenPropagated();
- public abstract AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi);
-
- public abstract boolean hasApiReferenceLevel();
-
public static OptionalBool isApiSafeForInlining(
MethodOptimizationInfo caller, MethodOptimizationInfo inlinee, InternalOptions options) {
if (!options.apiModelingOptions().enableApiCallerIdentification) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 9150ea0..d00377e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Optional;
import java.util.Set;
/**
@@ -36,6 +38,7 @@
private int readBits = 0;
private ClassTypeElement dynamicLowerBoundType = null;
private TypeElement dynamicUpperBoundType = null;
+ private Optional<AndroidApiLevel> apiReferenceLevel = null;
public MutableFieldOptimizationInfo fixupClassTypeReferences(
AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
@@ -150,4 +153,29 @@
public MutableFieldOptimizationInfo asMutableFieldOptimizationInfo() {
return this;
}
+
+ @SuppressWarnings("OptionalAssignedToNull")
+ @Override
+ public boolean hasApiReferenceLevel() {
+ return apiReferenceLevel != null;
+ }
+
+ @Override
+ public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+ assert hasApiReferenceLevel();
+ return apiReferenceLevel.orElse(minApi);
+ }
+
+ @Override
+ @SuppressWarnings("OptionalAssignedToNull")
+ public void setMinApiReferenceLevel() {
+ assert apiReferenceLevel == null;
+ this.apiReferenceLevel = Optional.empty();
+ }
+
+ @Override
+ public void setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+ assert apiReferenceLevel != null;
+ this.apiReferenceLevel = Optional.of(apiReferenceLevel);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index 95d817f..1b47801 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -498,6 +498,19 @@
}
@Override
+ @SuppressWarnings("OptionalAssignedToNull")
+ public void setMinApiReferenceLevel() {
+ assert apiReferenceLevel == null;
+ this.apiReferenceLevel = Optional.empty();
+ }
+
+ @Override
+ public void setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+ assert apiReferenceLevel != null;
+ this.apiReferenceLevel = Optional.of(apiReferenceLevel);
+ }
+
+ @Override
public boolean isMutableOptimizationInfo() {
return true;
}
@@ -512,11 +525,6 @@
return this;
}
- public MutableMethodOptimizationInfo setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
- this.apiReferenceLevel = Optional.ofNullable(apiReferenceLevel);
- return this;
- }
-
public MutableMethodOptimizationInfo mutableCopy() {
return new MutableMethodOptimizationInfo(this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
index bf34966..db7bd1d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableOptimizationInfo.java
@@ -4,4 +4,11 @@
package com.android.tools.r8.ir.optimize.info;
-public interface MutableOptimizationInfo {}
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public interface MutableOptimizationInfo {
+
+ void setMinApiReferenceLevel();
+
+ void setApiReferenceLevel(AndroidApiLevel apiReferenceLevel);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index b809e23..91b334c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.optimize.AssumeInserter;
@@ -410,6 +411,7 @@
ClassInitializerDefaultsResult.empty(),
feedback,
methodProcessor,
+ new MutableMethodConversionOptions(methodProcessor),
Timing.empty());
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 0206e81..74d7686 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.DexEncodedAnnotation;
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.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
@@ -29,7 +30,6 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueEnum;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.GraphLens;
@@ -522,9 +522,11 @@
break;
case ENUM:
- DexValueEnum en = value.asDexValueEnum();
+ DexField enumField = value.asDexValueEnum().getValue();
visitor.visitEnum(
- name, namingLens.lookupDescriptor(en.value.type).toString(), en.value.name.toString());
+ name,
+ namingLens.lookupDescriptor(enumField.getType()).toString(),
+ namingLens.lookupName(enumField).toString());
break;
case FIELD:
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 795c3bb..e6a829b 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -161,6 +161,22 @@
returnTypeDescriptor.equals("V") ? null : typeFromDescriptor(returnTypeDescriptor));
}
+ /** Get a method reference from class reference, method name and signature. */
+ public static MethodReference methodFromDescriptor(
+ ClassReference classReference, String methodName, String methodDescriptor) {
+ ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+ for (String parameterTypeDescriptor :
+ DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor)) {
+ builder.add(typeFromDescriptor(parameterTypeDescriptor));
+ }
+ String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+ return method(
+ classReference,
+ methodName,
+ builder.build(),
+ returnTypeDescriptor.equals("V") ? null : typeFromDescriptor(returnTypeDescriptor));
+ }
+
public static MethodReference classConstructor(ClassReference type) {
return method(type, "<clinit>", Collections.emptyList(), null);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
index 4fdad6a..fedb75d 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationFixer.java
@@ -40,10 +40,8 @@
}
private void processMethod(DexEncodedMethod method) {
- method.setAnnotations(method.annotations().rewrite(this::rewriteAnnotation));
- method.parameterAnnotationsList =
- method.parameterAnnotationsList.rewrite(
- dexAnnotationSet -> dexAnnotationSet.rewrite(this::rewriteAnnotation));
+ method.rewriteAllAnnotations(
+ (annotation, isParameterAnnotation) -> rewriteAnnotation(annotation));
}
private void processField(DexEncodedField field) {
@@ -73,7 +71,7 @@
if (value.isDexValueArray()) {
DexValue[] originalValues = value.asDexValueArray().getValues();
DexValue[] rewrittenValues =
- ArrayUtils.map(DexValue[].class, originalValues, this::rewriteComplexValue);
+ ArrayUtils.map(originalValues, this::rewriteComplexValue, DexValue.EMPTY_ARRAY);
if (rewrittenValues != originalValues) {
return new DexValueArray(rewrittenValues);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 567b7cb..ac1ad75 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -6,24 +6,28 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedMember;
-import com.android.tools.r8.graph.DexEncodedMethod;
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.EnclosingMethodAttribute;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
import com.android.tools.r8.kotlin.KotlinPropertyInfo;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
public class AnnotationRemover {
@@ -49,49 +53,50 @@
}
/** Used to filter annotations on classes, methods and fields. */
- private boolean filterAnnotations(DexDefinition holder, DexAnnotation annotation) {
+ private boolean filterAnnotations(
+ ProgramDefinition holder, DexAnnotation annotation, AnnotatedKind kind) {
return annotationsToRetain.contains(annotation)
- || shouldKeepAnnotation(appView, holder, annotation, isAnnotationTypeLive(annotation));
- }
-
- public static boolean shouldKeepAnnotation(
- AppView<AppInfoWithLiveness> appView, DexDefinition holder, DexAnnotation annotation) {
- return shouldKeepAnnotation(
- appView, holder, annotation, isAnnotationTypeLive(annotation, appView));
+ || shouldKeepAnnotation(
+ appView, holder, annotation, isAnnotationTypeLive(annotation), kind);
}
public static boolean shouldKeepAnnotation(
AppView<?> appView,
- DexDefinition holder,
+ ProgramDefinition holder,
DexAnnotation annotation,
- boolean isAnnotationTypeLive) {
+ boolean isAnnotationTypeLive,
+ AnnotatedKind kind) {
// If we cannot run the AnnotationRemover we are keeping the annotation.
if (!appView.options().isShrinking()) {
return true;
}
+
+ InternalOptions options = appView.options();
ProguardKeepAttributes config =
- appView.options().getProguardConfiguration() != null
- ? appView.options().getProguardConfiguration().getKeepAttributes()
+ options.getProguardConfiguration() != null
+ ? options.getProguardConfiguration().getKeepAttributes()
: ProguardKeepAttributes.fromPatterns(ImmutableList.of());
DexItemFactory dexItemFactory = appView.dexItemFactory();
-
switch (annotation.visibility) {
case DexAnnotation.VISIBILITY_SYSTEM:
+ if (kind.isParameter()) {
+ return false;
+ }
// InnerClass and EnclosingMember are represented in class attributes, not annotations.
assert !DexAnnotation.isInnerClassAnnotation(annotation, dexItemFactory);
assert !DexAnnotation.isMemberClassesAnnotation(annotation, dexItemFactory);
assert !DexAnnotation.isEnclosingMethodAnnotation(annotation, dexItemFactory);
assert !DexAnnotation.isEnclosingClassAnnotation(annotation, dexItemFactory);
- assert appView.options().passthroughDexCode
+ assert options.passthroughDexCode
|| !DexAnnotation.isSignatureAnnotation(annotation, dexItemFactory);
if (config.exceptions && DexAnnotation.isThrowingAnnotation(annotation, dexItemFactory)) {
return true;
}
if (DexAnnotation.isSourceDebugExtension(annotation, dexItemFactory)) {
- assert holder.isDexClass();
+ assert holder.isProgramClass();
appView.setSourceDebugExtensionForType(
- holder.asDexClass(), annotation.annotation.elements[0].value.asDexValueString());
+ holder.asProgramClass(), annotation.annotation.elements[0].value.asDexValueString());
return config.sourceDebugExtension;
}
if (config.methodParameters
@@ -106,14 +111,35 @@
return false;
case DexAnnotation.VISIBILITY_RUNTIME:
- if (!config.runtimeVisibleAnnotations) {
- return false;
+ // We always keep the @java.lang.Retention annotation on annotation classes, since the
+ // removal of this annotation may change the annotation from being runtime visible to
+ // runtime invisible.
+ if (holder.isProgramClass()
+ && holder.asProgramClass().isAnnotation()
+ && DexAnnotation.isJavaLangRetentionAnnotation(annotation, dexItemFactory)) {
+ return true;
+ }
+
+ if (kind.isParameter()) {
+ if (!options.isKeepRuntimeVisibleParameterAnnotationsEnabled()) {
+ return false;
+ }
+ } else {
+ if (!options.isKeepRuntimeVisibleAnnotationsEnabled()) {
+ return false;
+ }
}
return isAnnotationTypeLive;
case DexAnnotation.VISIBILITY_BUILD:
- if (!config.runtimeInvisibleAnnotations) {
- return false;
+ if (kind.isParameter()) {
+ if (!options.isKeepRuntimeInvisibleParameterAnnotationsEnabled()) {
+ return false;
+ }
+ } else {
+ if (!options.isKeepRuntimeInvisibleAnnotationsEnabled()) {
+ return false;
+ }
}
return isAnnotationTypeLive;
@@ -132,60 +158,35 @@
return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
}
- /**
- * Used to filter annotations on parameters.
- */
- private boolean filterParameterAnnotations(DexAnnotation annotation) {
- if (annotationsToRetain.contains(annotation)) {
- return true;
- }
- switch (annotation.visibility) {
- case DexAnnotation.VISIBILITY_SYSTEM:
- return false;
- case DexAnnotation.VISIBILITY_RUNTIME:
- if (!keep.runtimeVisibleParameterAnnotations) {
- return false;
- }
- break;
- case DexAnnotation.VISIBILITY_BUILD:
- if (!keep.runtimeInvisibleParameterAnnotations) {
- return false;
- }
- break;
- default:
- throw new Unreachable("Unexpected annotation visibility.");
- }
- return isAnnotationTypeLive(annotation);
- }
-
public AnnotationRemover ensureValid() {
keep.ensureValid(appView.options().forceProguardCompatibility);
return this;
}
- public void run() {
- for (DexProgramClass clazz : appView.appInfo().classes()) {
- stripAttributes(clazz);
- clazz.setAnnotations(
- clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
- // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
- // properties are split over fields and methods. Check if any is pinned before pruning the
- // information.
- Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
- clazz.forEachProgramMember(
- member -> processMember(member.getDefinition(), clazz, pinnedKotlinProperties));
- clazz.forEachProgramMember(
- member -> {
- KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
- if (kotlinInfo.isProperty()
- && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
- member.clearKotlinInfo();
- }
- });
- }
+ public void run(ExecutorService executorService) throws ExecutionException {
+ ThreadUtils.processItems(appView.appInfo().classes(), this::run, executorService);
assert verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo();
}
+ private void run(DexProgramClass clazz) {
+ KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
+ removeAnnotations(clazz, keepInfo);
+ stripAttributes(clazz, keepInfo);
+ // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
+ // properties are split over fields and methods. Check if any is pinned before pruning the
+ // information.
+ Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
+ clazz.forEachProgramMember(member -> processMember(member, clazz, pinnedKotlinProperties));
+ clazz.forEachProgramMember(
+ member -> {
+ KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
+ if (kotlinInfo.isProperty()
+ && !pinnedKotlinProperties.contains(kotlinInfo.asProperty())) {
+ member.clearKotlinInfo();
+ }
+ });
+ }
+
private boolean verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo() {
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (clazz.getKotlinInfo().isNoKotlinInformation()) {
@@ -200,17 +201,11 @@
}
private void processMember(
- DexEncodedMember<?, ?> member,
+ ProgramMember<?, ?> member,
DexProgramClass clazz,
Set<KotlinPropertyInfo> pinnedKotlinProperties) {
- member.setAnnotations(
- member.annotations().rewrite(annotation -> rewriteAnnotation(member, annotation)));
- if (member.isDexEncodedMethod()) {
- DexEncodedMethod method = member.asDexEncodedMethod();
- method.parameterAnnotationsList =
- method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
- }
- KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member, clazz);
+ KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member);
+ removeAnnotations(member, memberInfo);
if (memberInfo.isSignatureAttributeRemovalAllowed(options)) {
member.clearGenericSignature();
}
@@ -223,9 +218,10 @@
}
}
- private DexAnnotation rewriteAnnotation(DexDefinition holder, DexAnnotation original) {
+ private DexAnnotation rewriteAnnotation(
+ ProgramDefinition holder, DexAnnotation original, AnnotatedKind kind) {
// Check if we should keep this annotation first.
- if (filterAnnotations(holder, original)) {
+ if (filterAnnotations(holder, original, kind)) {
// Then, filter out values that refer to dead definitions.
return original.rewrite(this::rewriteEncodedAnnotation);
}
@@ -265,12 +261,28 @@
return liveGetter ? original : null;
}
- private void stripAttributes(DexProgramClass clazz) {
+ private void removeAnnotations(ProgramDefinition definition, KeepInfo<?, ?> keepInfo) {
+ boolean isAnnotation =
+ definition.isProgramClass() && definition.asProgramClass().isAnnotation();
+ if ((options.isForceProguardCompatibilityEnabled() || keepInfo.isPinned())) {
+ definition.rewriteAllAnnotations(
+ (annotation, kind) -> rewriteAnnotation(definition, annotation, kind));
+ } else if (!isAnnotation) {
+ definition.clearAllAnnotations();
+ } else {
+ definition.rewriteAllAnnotations(
+ (annotation, isParameterAnnotation) ->
+ DexAnnotation.isJavaLangRetentionAnnotation(annotation, appView.dexItemFactory())
+ ? annotation
+ : null);
+ }
+ }
+
+ private void stripAttributes(DexProgramClass clazz, KeepClassInfo keepInfo) {
// If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
// need to keep the enclosing method and inner classes attributes, if requested. In Proguard
// compatibility mode we keep these attributes independent of whether the given class is kept.
// In full mode we remove the attribute if not both sides are kept.
- KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
clazz.removeEnclosingMethodAttribute(
enclosingMethodAttribute ->
keepInfo.isEnclosingMethodAttributeRemovalAllowed(
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index cb49580..da86fe5 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -91,46 +91,55 @@
@Override
public void registerInstanceFieldRead(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceInstanceFieldRead(field, context);
}
@Override
public void registerInstanceFieldReadFromMethodHandle(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceInstanceFieldReadFromMethodHandle(field, context);
}
@Override
public void registerInstanceFieldWrite(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceInstanceFieldWrite(field, context);
}
@Override
public void registerInstanceFieldWriteFromMethodHandle(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context);
}
@Override
public void registerNewInstance(DexType type) {
+ setMaxApiReferenceLevel(type);
enqueuer.traceNewInstance(type, context);
}
@Override
public void registerStaticFieldRead(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceStaticFieldRead(field, context);
}
@Override
public void registerStaticFieldReadFromMethodHandle(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceStaticFieldReadFromMethodHandle(field, context);
}
@Override
public void registerStaticFieldWrite(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceStaticFieldWrite(field, context);
}
@Override
public void registerStaticFieldWriteFromMethodHandle(DexField field) {
+ setMaxApiReferenceLevel(field);
enqueuer.traceStaticFieldWriteFromMethodHandle(field, context);
}
@@ -146,6 +155,11 @@
}
@Override
+ public void registerSafeCheckCast(DexType type) {
+ enqueuer.traceSafeCheckCast(type, context);
+ }
+
+ @Override
public void registerTypeReference(DexType type) {
enqueuer.traceTypeReference(type, context);
}
@@ -172,10 +186,16 @@
enqueuer.traceCallSite(callSite, context);
}
- private void setMaxApiReferenceLevel(DexMethod invokedMethod) {
+ private void setMaxApiReferenceLevel(DexReference reference) {
+ if (reference.isDexMember()) {
+ this.maxApiReferenceLevel =
+ maxApiReferenceLevel.max(
+ reference
+ .asDexMember()
+ .computeApiLevelForReferencedTypes(appView, apiReferenceMapping));
+ }
this.maxApiReferenceLevel =
- maxApiReferenceLevel.max(
- apiReferenceMapping.getOrDefault(invokedMethod, maxApiReferenceLevel));
+ maxApiReferenceLevel.max(apiReferenceMapping.getOrDefault(reference, maxApiReferenceLevel));
}
public AndroidApiLevel getMaxApiReferenceLevel() {
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 743fa3c..75bb5f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
-import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
@@ -31,6 +30,7 @@
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.ClasspathOrLibraryDefinition;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotation.AnnotatedKind;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
@@ -42,7 +42,6 @@
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexItemFactory.ClassMethods;
import com.android.tools.r8.graph.DexLibraryClass;
@@ -80,6 +79,7 @@
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.analysis.ApiModelAnalysis;
import com.android.tools.r8.graph.analysis.DesugaredLibraryConversionWrapperAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
@@ -102,8 +102,6 @@
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -171,11 +169,11 @@
/**
* Approximates the runtime dependencies for the given set of roots.
- * <p>
+ *
* <p>The implementation filters the static call-graph with liveness information on classes to
* remove virtual methods that are reachable by their static type but are unreachable at runtime as
* they are not visible from any instance.
- * <p>
+ *
* <p>As result of the analysis, an instance of {@link AppInfoWithLiveness} is returned. See the
* field descriptions for details.
*/
@@ -395,6 +393,13 @@
private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
new IdentityHashMap<>();
+ /**
+ * A map from annotation classes to parameter annotations that need to be processed should the
+ * classes ever become live.
+ */
+ private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>>
+ deferredParameterAnnotations = new IdentityHashMap<>();
+
/** Map of active if rules to speed up aapt2 generated keep rules. */
private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
@@ -470,14 +475,7 @@
}
referenceToApiLevelMap = new IdentityHashMap<>();
if (options.apiModelingOptions().enableApiCallerIdentification) {
- options
- .apiModelingOptions()
- .methodApiMapping
- .forEach(
- (methodReference, apiLevel) -> {
- referenceToApiLevelMap.put(
- options.dexItemFactory().createMethod(methodReference), apiLevel);
- });
+ options.apiModelingOptions().appendToApiLevelMap(referenceToApiLevelMap, dexItemFactory);
}
}
@@ -1040,6 +1038,11 @@
traceConstClassOrCheckCast(type, currentMethod);
}
+ void traceSafeCheckCast(DexType type, ProgramMethod currentMethod) {
+ checkCastAnalyses.forEach(analysis -> analysis.traceSafeCheckCast(type, currentMethod));
+ traceCompilerSynthesizedConstClassOrCheckCast(type, currentMethod);
+ }
+
void traceConstClass(
DexType type,
ProgramMethod currentMethod,
@@ -1100,8 +1103,21 @@
}
private void traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
+ internalTraceConstClassOrCheckCast(type, currentMethod, false);
+ }
+
+ // TODO(b/190487539): Currently only used by traceSafeCheckCast(), but should also be used to
+ // ensure we don't trigger compat behavior for const-class instructions synthesized for
+ // synchronized methods.
+ private void traceCompilerSynthesizedConstClassOrCheckCast(
+ DexType type, ProgramMethod currentMethod) {
+ internalTraceConstClassOrCheckCast(type, currentMethod, true);
+ }
+
+ private void internalTraceConstClassOrCheckCast(
+ DexType type, ProgramMethod currentMethod, boolean isCompilerSynthesized) {
traceTypeReference(type, currentMethod);
- if (!forceProguardCompatibility) {
+ if (!forceProguardCompatibility || isCompilerSynthesized) {
return;
}
DexType baseType = type.toBaseType(appView.dexItemFactory());
@@ -1688,7 +1704,7 @@
return;
}
if (!type.isClassType()) {
- // Ignore primitive types.
+ // Ignore primitive types and void.
return;
}
DexProgramClass clazz = getProgramClassOrNull(type, context);
@@ -1727,7 +1743,6 @@
assert !mode.isFinalMainDexTracing()
|| !options.testing.checkForNotExpandingMainDexTracingResult
|| appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
- || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
: "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
// Mark types in inner-class attributes referenced.
@@ -1819,16 +1834,9 @@
// If this type has deferred annotations, we have to process those now, too.
if (clazz.isAnnotation()) {
- Map<DexAnnotation, List<ProgramDefinition>> annotations =
- deferredAnnotations.remove(clazz.getType());
- if (annotations != null) {
- assert annotations.keySet().stream()
- .allMatch(a -> a.getAnnotationType() == clazz.getType());
- annotations.forEach(
- (annotation, annotatedItems) ->
- annotatedItems.forEach(
- annotatedItem -> processAnnotation(annotatedItem, annotation)));
- }
+ processDeferredAnnotations(clazz, deferredAnnotations, AnnotatedKind::from);
+ processDeferredAnnotations(
+ clazz, deferredParameterAnnotations, annotatedItem -> AnnotatedKind.PARAMETER);
}
rootSet.forEachDependentInstanceConstructor(
@@ -1840,6 +1848,24 @@
analyses.forEach(analysis -> analysis.processNewlyLiveClass(clazz, workList));
}
+ private void processDeferredAnnotations(
+ DexProgramClass clazz,
+ Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations,
+ Function<ProgramDefinition, AnnotatedKind> kindProvider) {
+ Map<DexAnnotation, List<ProgramDefinition>> annotations =
+ deferredAnnotations.remove(clazz.getType());
+ if (annotations != null) {
+ assert annotations.keySet().stream()
+ .allMatch(annotation -> annotation.getAnnotationType() == clazz.getType());
+ annotations.forEach(
+ (annotation, annotatedItems) ->
+ annotatedItems.forEach(
+ annotatedItem ->
+ processAnnotation(
+ annotatedItem, annotation, kindProvider.apply(annotatedItem))));
+ }
+ }
+
private void ensureMethodsContinueToWidenAccess(ClassDefinition clazz) {
assert !clazz.isProgramClass();
ScopedDexMethodSet seen =
@@ -1922,27 +1948,35 @@
}
private void processAnnotations(ProgramDefinition annotatedItem) {
- processAnnotations(annotatedItem, annotatedItem.getDefinition().annotations());
+ processAnnotations(
+ annotatedItem,
+ annotatedItem.getDefinition().annotations(),
+ AnnotatedKind.from(annotatedItem));
}
- private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotationSet annotations) {
- processAnnotations(annotatedItem, annotations.annotations);
+ private void processAnnotations(
+ ProgramDefinition annotatedItem, DexAnnotationSet annotations, AnnotatedKind kind) {
+ processAnnotations(annotatedItem, annotations.annotations, kind);
}
- private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotation[] annotations) {
+ private void processAnnotations(
+ ProgramDefinition annotatedItem, DexAnnotation[] annotations, AnnotatedKind kind) {
for (DexAnnotation annotation : annotations) {
- processAnnotation(annotatedItem, annotation);
+ processAnnotation(annotatedItem, annotation, kind);
}
}
- private void processAnnotation(ProgramDefinition annotatedItem, DexAnnotation annotation) {
+ private void processAnnotation(
+ ProgramDefinition annotatedItem, DexAnnotation annotation, AnnotatedKind kind) {
DexType type = annotation.getAnnotationType();
DexClass clazz = definitionFor(type, annotatedItem);
boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
- if (!shouldKeepAnnotation(appView, annotatedItem.getDefinition(), annotation, isLive)) {
+ if (!shouldKeepAnnotation(appView, annotatedItem, annotation, isLive, kind)) {
// Remember this annotation for later.
if (!annotationTypeIsLibraryClass) {
+ Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
+ kind.isParameter() ? deferredParameterAnnotations : this.deferredAnnotations;
Map<DexAnnotation, List<ProgramDefinition>> deferredAnnotationsForAnnotationType =
deferredAnnotations.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
deferredAnnotationsForAnnotationType
@@ -1951,11 +1985,14 @@
}
return;
}
- KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
- graphReporter.registerAnnotation(annotation, reason);
+
+ // Report that the annotation is retained due to the annotated item.
+ graphReporter.registerAnnotation(annotation, annotatedItem);
+
+ // Report that the items referenced from inside the annotation are retained due to the
+ // annotation.
AnnotationReferenceMarker referenceMarker =
- new AnnotationReferenceMarker(
- annotation.getAnnotationType(), annotatedItem, appView.dexItemFactory(), reason);
+ new AnnotationReferenceMarker(annotation, annotatedItem);
annotation.annotation.collectIndexedItems(referenceMarker);
}
@@ -2990,6 +3027,9 @@
&& appView.options().getProguardConfiguration().getKeepAttributes().signature) {
registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
}
+ if (appView.options().apiModelingOptions().enableApiCallerIdentification) {
+ registerAnalysis(new ApiModelAnalysis(appView, referenceToApiLevelMap));
+ }
if (mode.isInitialTreeShaking()) {
// This is simulating the effect of the "root set" applied rules.
// This is done only in the initial pass, in subsequent passes the "rules" are reapplied
@@ -3870,8 +3910,6 @@
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(definition));
- checkDefinitionForSoftPinning(method);
-
// Notify analyses.
analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
}
@@ -3896,12 +3934,14 @@
}
void traceMethodDefinitionExcludingCode(ProgramMethod method) {
+ checkDefinitionForSoftPinning(method);
markReferencedTypesAsLive(method);
processAnnotations(method);
method
.getDefinition()
.getParameterAnnotations()
- .forEachAnnotation(annotation -> processAnnotation(method, annotation));
+ .forEachAnnotation(
+ annotation -> processAnnotation(method, annotation, AnnotatedKind.PARAMETER));
}
private void traceNonDesugaredCode(ProgramMethod method) {
@@ -3917,19 +3957,8 @@
DefaultEnqueuerUseRegistry registry =
useRegistryFactory.create(appView, method, this, referenceToApiLevelMap);
method.registerCodeReferences(registry);
- DexEncodedMethod methodDefinition = method.getDefinition();
- AndroidApiLevel maxApiReferenceLevel = registry.getMaxApiReferenceLevel();
- assert maxApiReferenceLevel.isGreaterThanOrEqualTo(options.minApiLevel);
- // To not have mutable update information for all methods that all has min api level we
- // swap the default optimization info for one with that marks the api level to be min api.
- if (methodDefinition.getOptimizationInfo() == DefaultMethodOptimizationInfo.getInstance()
- && maxApiReferenceLevel == options.minApiLevel) {
- methodDefinition.setMinApiOptimizationInfo(
- DefaultMethodOptimizationWithMinApiInfo.getInstance());
- return;
- }
- methodDefinition.setOptimizationInfo(
- methodDefinition.getMutableOptimizationInfo().setApiReferenceLevel(maxApiReferenceLevel));
+ // Notify analyses.
+ analyses.forEach(analysis -> analysis.processTracedCode(method, registry));
}
private void checkDefinitionForSoftPinning(ProgramDefinition definition) {
@@ -4490,20 +4519,12 @@
private class AnnotationReferenceMarker implements IndexedItemCollection {
- private final DexItem annotationHolder;
private final ProgramDefinition context;
- private final DexItemFactory dexItemFactory;
private final KeepReason reason;
- private AnnotationReferenceMarker(
- DexItem annotationHolder,
- ProgramDefinition context,
- DexItemFactory dexItemFactory,
- KeepReason reason) {
- this.annotationHolder = annotationHolder;
+ private AnnotationReferenceMarker(DexAnnotation annotation, ProgramDefinition context) {
this.context = context;
- this.dexItemFactory = dexItemFactory;
- this.reason = reason;
+ this.reason = KeepReason.referencedInAnnotation(annotation, context);
}
@Override
@@ -4533,17 +4554,15 @@
: fieldAccessInfoCollection.extend(
fieldReference, new FieldAccessInfoImpl(fieldReference));
fieldAccessInfo.setReadFromAnnotation();
- markFieldAsLive(field, context, KeepReason.referencedInAnnotation(annotationHolder));
+ markFieldAsLive(field, context, reason);
// When an annotation has a field of an enum type the JVM will use the values() method on
// that enum class if the field is referenced.
if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
- markEnumValuesAsReachable(
- field.getHolder(), KeepReason.referencedInAnnotation(annotationHolder));
+ markEnumValuesAsReachable(field.getHolder(), reason);
}
} else {
// There is no dispatch on annotations, so only keep what is directly referenced.
- workList.enqueueMarkFieldAsReachableAction(
- field, context, KeepReason.referencedInAnnotation(annotationHolder));
+ workList.enqueueMarkFieldAsReachableAction(field, context, reason);
}
return false;
}
@@ -4560,17 +4579,13 @@
if (target != null) {
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target.getReference() == method) {
- markDirectStaticOrConstructorMethodAsLive(
- new ProgramMethod(holder, target),
- KeepReason.referencedInAnnotation(annotationHolder));
+ markDirectStaticOrConstructorMethodAsLive(new ProgramMethod(holder, target), reason);
}
} else {
target = holder.lookupVirtualMethod(method);
// There is no dispatch on annotations, so only keep what is directly referenced.
if (target != null && target.getReference() == method) {
- markMethodAsTargeted(
- new ProgramMethod(holder, target),
- KeepReason.referencedInAnnotation(annotationHolder));
+ markMethodAsTargeted(new ProgramMethod(holder, target), reason);
}
}
return false;
@@ -4598,11 +4613,7 @@
@Override
public boolean addType(DexType type) {
- // Annotations can also contain the void type, which is not a class type, so filter it out
- // here.
- if (type != dexItemFactory.voidType) {
- markTypeAsLive(type, context, reason);
- }
+ markTypeAsLive(type, context, reason);
return false;
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index 07f625d..e509230 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -22,5 +22,11 @@
boolean isKeepInnerClassesAttributeEnabled();
+ boolean isKeepRuntimeInvisibleAnnotationsEnabled();
+
+ boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled();
+
boolean isKeepRuntimeVisibleAnnotationsEnabled();
+
+ boolean isKeepRuntimeVisibleParameterAnnotationsEnabled();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 963e036..6146c2c 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
@@ -21,7 +20,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.DexItem;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
@@ -51,7 +49,7 @@
private final CollectingGraphConsumer verificationGraphConsumer;
// Canonicalization of external graph-nodes and edge info.
- private final Map<DexItem, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
+ private final Map<DexAnnotation, AnnotationGraphNode> annotationNodes = new IdentityHashMap<>();
private final Map<DexType, ClassGraphNode> classNodes = new IdentityHashMap<>();
private final Map<DexMethod, MethodGraphNode> methodNodes = new IdentityHashMap<>();
private final Map<DexField, FieldGraphNode> fieldNodes = new IdentityHashMap<>();
@@ -359,11 +357,13 @@
return registerEdge(getClassGraphNode(clazz.type), reason);
}
- public KeepReasonWitness registerAnnotation(DexAnnotation annotation, KeepReason reason) {
+ public KeepReasonWitness registerAnnotation(
+ DexAnnotation annotation, ProgramDefinition annotatedItem) {
+ KeepReason reason = KeepReason.annotatedOn(annotatedItem.getDefinition());
if (skipReporting(reason)) {
return KeepReasonWitness.INSTANCE;
}
- return registerEdge(getAnnotationGraphNode(annotation.annotation.type), reason);
+ return registerEdge(getAnnotationGraphNode(annotation, annotatedItem), reason);
}
public KeepReasonWitness registerMethod(DexEncodedMethod method, KeepReason reason) {
@@ -432,15 +432,18 @@
return appView.appInfo().definitionForWithoutExistenceAssert(type);
}
- AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
+ AnnotationGraphNode getAnnotationGraphNode(
+ DexAnnotation annotation, ProgramDefinition annotatedItem) {
return annotationNodes.computeIfAbsent(
- type,
- t -> {
- if (t instanceof DexType) {
- return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
- }
- throw new Unimplemented(
- "Incomplete support for annotation node on item: " + type.getClass());
+ annotation,
+ key -> {
+ GraphNode annotatedNode =
+ annotatedItem
+ .getReference()
+ .apply(
+ this::getClassGraphNode, this::getFieldGraphNode, this::getMethodGraphNode);
+ ClassGraphNode annotationClassNode = getClassGraphNode(annotation.getAnnotationType());
+ return new AnnotationGraphNode(annotatedNode, annotationClassNode);
});
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 7e9b166..ddd5bfc 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMember;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
import com.android.tools.r8.utils.InternalOptions;
@@ -86,6 +87,10 @@
return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz);
}
+ public final KeepMemberInfo<?, ?> getMemberInfo(ProgramMember<?, ?> member) {
+ return getMemberInfo(member.getDefinition(), member.getHolder());
+ }
+
public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
return getMethodInfo(method.getDefinition(), method.getHolder());
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 5e6b3fd..2493dd2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -6,12 +6,13 @@
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItem;
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.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
// TODO(herhut): Canonicalize reason objects.
@@ -61,8 +62,9 @@
return new ReferencedFrom(method.getDefinition());
}
- public static KeepReason referencedInAnnotation(DexItem holder) {
- return new ReferencedInAnnotation(holder);
+ public static KeepReason referencedInAnnotation(
+ DexAnnotation annotation, ProgramDefinition annotatedItem) {
+ return new ReferencedInAnnotation(annotation, annotatedItem);
}
public boolean isDueToKeepRule() {
@@ -229,10 +231,12 @@
private static class ReferencedInAnnotation extends KeepReason {
- private final DexItem holder;
+ private final DexAnnotation annotation;
+ private final ProgramDefinition annotatedItem;
- private ReferencedInAnnotation(DexItem holder) {
- this.holder = holder;
+ private ReferencedInAnnotation(DexAnnotation annotation, ProgramDefinition annotatedItem) {
+ this.annotation = annotation;
+ this.annotatedItem = annotatedItem;
}
@Override
@@ -242,7 +246,7 @@
@Override
public GraphNode getSourceNode(GraphReporter graphReporter) {
- return graphReporter.getAnnotationGraphNode(holder);
+ return graphReporter.getAnnotationGraphNode(annotation, annotatedItem);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index fc2756a..67f1c75 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -61,6 +61,11 @@
}
@Override
+ public void traceSafeCheckCast(DexType type, ProgramMethod context) {
+ // Intentionally empty.
+ }
+
+ @Override
public void traceInstanceOf(DexType type, ProgramMethod context) {
add(type, instanceOfTypes);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 65c510c..737ffa6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -43,6 +43,7 @@
private DexType superType;
private DexTypeList interfaces = DexTypeList.empty();
private DexString sourceFile = null;
+ private boolean useSortedMethodBacking = false;
private List<DexEncodedField> staticFields = new ArrayList<>();
private List<DexEncodedField> instanceFields = new ArrayList<>();
private List<DexEncodedMethod> directMethods = new ArrayList<>();
@@ -143,6 +144,11 @@
return self();
}
+ public B setUseSortedMethodBacking(boolean useSortedMethodBacking) {
+ this.useSortedMethodBacking = useSortedMethodBacking;
+ return self();
+ }
+
public C build() {
int flag = isAbstract ? Constants.ACC_ABSTRACT : Constants.ACC_FINAL;
int itfFlag = isInterface ? Constants.ACC_INTERFACE : 0;
@@ -167,27 +173,34 @@
+ 11 * (long) virtualMethods.hashCode()
+ 13 * (long) staticFields.hashCode()
+ 17 * (long) instanceFields.hashCode();
- return getClassKind()
- .create(
- type,
- originKind,
- origin,
- accessFlags,
- superType,
- interfaces,
- sourceFile,
- nestHost,
- nestMembers,
- enclosingMembers,
- innerClasses,
- signature,
- DexAnnotationSet.empty(),
- staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
- instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
- directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
- virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
- factory.getSkipNameValidationForTesting(),
- c -> checksum,
- null);
+ C clazz =
+ getClassKind()
+ .create(
+ type,
+ originKind,
+ origin,
+ accessFlags,
+ superType,
+ interfaces,
+ sourceFile,
+ nestHost,
+ nestMembers,
+ enclosingMembers,
+ innerClasses,
+ signature,
+ DexAnnotationSet.empty(),
+ staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
+ instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
+ DexEncodedMethod.EMPTY_ARRAY,
+ DexEncodedMethod.EMPTY_ARRAY,
+ factory.getSkipNameValidationForTesting(),
+ c -> checksum,
+ null);
+ if (useSortedMethodBacking) {
+ clazz.getMethodCollection().useSortedBacking();
+ }
+ clazz.setDirectMethods(directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+ clazz.setVirtualMethods(virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY));
+ return clazz;
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 2d0970f..3bd99a2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -541,7 +541,15 @@
}
assert !isSyntheticClass(type);
DexProgramClass dexProgramClass =
- internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
+ internalCreateClass(
+ kind,
+ syntheticProgramClassBuilder -> {
+ syntheticProgramClassBuilder.setUseSortedMethodBacking(true);
+ fn.accept(syntheticProgramClassBuilder);
+ },
+ outerContext,
+ type,
+ appView.dexItemFactory());
onCreationConsumer.accept(dexProgramClass);
return dexProgramClass;
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 6ea2f40..1e4082b 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -43,6 +43,8 @@
S(31),
UNKNOWN(10000);
+ // When updating LATEST and a new version goes stable, add a new api-versions.xml to third_party
+ // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest.
public static final AndroidApiLevel LATEST = S;
public static final int magicApiLevelUsedByAndroidPlatformBuild = 10000;
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 4fc6914..9a863be 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -80,6 +80,10 @@
clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
}
+ public static <T> boolean isEmpty(T[] array) {
+ return array.length == 0;
+ }
+
public static boolean isSorted(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
assert array[i] < array[i + 1];
@@ -94,18 +98,6 @@
/**
* Rewrites the input array based on the given function.
*
- * @param clazz target type's Class to cast
- * @param original an array of original elements
- * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
- * @return an array with written elements
- */
- public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
- return map(original, mapper, clazz.cast(Array.newInstance(clazz.getComponentType(), 0)));
- }
-
- /**
- * Rewrites the input array based on the given function.
- *
* @param original an array of original elements
* @param mapper a mapper that rewrites an original element to a new one, maybe `null`
* @param emptyArray an empty array
@@ -179,4 +171,8 @@
newArray[ts.length] = element;
return newArray;
}
+
+ public static <T> T first(T[] ts) {
+ return ts[0];
+ }
}
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 f96e8ce..b75ac5a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
@@ -617,10 +618,25 @@
}
@Override
+ public boolean isKeepRuntimeInvisibleAnnotationsEnabled() {
+ return proguardConfiguration.getKeepAttributes().runtimeInvisibleAnnotations;
+ }
+
+ @Override
+ public boolean isKeepRuntimeInvisibleParameterAnnotationsEnabled() {
+ return proguardConfiguration.getKeepAttributes().runtimeInvisibleParameterAnnotations;
+ }
+
+ @Override
public boolean isKeepRuntimeVisibleAnnotationsEnabled() {
return proguardConfiguration.getKeepAttributes().runtimeVisibleAnnotations;
}
+ @Override
+ public boolean isKeepRuntimeVisibleParameterAnnotationsEnabled() {
+ return proguardConfiguration.getKeepAttributes().runtimeVisibleParameterAnnotations;
+ }
+
/**
* If any non-static class merging is enabled, information about types referred to by instanceOf
* and check cast instructions needs to be collected.
@@ -1306,6 +1322,19 @@
public Map<TypeReference, AndroidApiLevel> typeApiMapping = new HashMap<>();
public boolean enableApiCallerIdentification = false;
+
+ public void appendToApiLevelMap(
+ Map<DexReference, AndroidApiLevel> apiLevelMap, DexItemFactory factory) {
+ methodApiMapping.forEach(
+ (methodReference, apiLevel) ->
+ apiLevelMap.put(factory.createMethod(methodReference), apiLevel));
+ fieldApiMapping.forEach(
+ (fieldReference, apiLevel) ->
+ apiLevelMap.put(factory.createField(fieldReference), apiLevel));
+ typeApiMapping.forEach(
+ (typeReference, apiLevel) ->
+ apiLevelMap.put(factory.createType(typeReference.getDescriptor()), apiLevel));
+ }
}
public static class ProtoShrinkingOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 98ea2c4..070e63f 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -25,6 +25,12 @@
return result;
}
+ public static <T> Set<T> newIdentityHashSet(ForEachable<T> forEachable) {
+ Set<T> result = Sets.newIdentityHashSet();
+ forEachable.forEach(result::add);
+ return result;
+ }
+
public static <T> Set<T> newIdentityHashSet(Iterable<T> c) {
Set<T> result = Sets.newIdentityHashSet();
c.forEach(result::add);
diff --git a/src/main/java/com/android/tools/r8/utils/SupplierUtils.java b/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
index 13a02cb..84dac5f 100644
--- a/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
+import com.google.common.base.Suppliers;
import java.util.function.Supplier;
public class SupplierUtils {
@@ -12,4 +13,15 @@
Box<T> box = new Box<>();
return () -> box.computeIfAbsent(supplier);
}
+
+ public static <T, E extends Throwable> Supplier<T> memoize(ThrowingSupplier<T, E> supplier) {
+ return Suppliers.memoize(
+ () -> {
+ try {
+ return supplier.get();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index 294a210..df59f20 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
/** Two value continuation value to indicate the continuation of a loop/traversal. */
+/* This class is used for building up api class member traversals. */
public enum TraversalContinuation {
CONTINUE,
BREAK;
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index e9e7a58..064d98f 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -7,6 +7,7 @@
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
import lambdadesugaringnplus.other.ClassWithDefaultPackagePrivate;
import lambdadesugaringnplus.other.InterfaceWithDefaultPackagePrivate;
@@ -372,8 +373,15 @@
// I don't know how to keep this method moved to the companion class
// without the direct call.
AnnotatedInterface.annotatedStaticMethod();
- if (checkAnnotationValue(
- getCompanionClassOrInterface().getMethod("annotatedStaticMethod").getAnnotations(), 4)) {
+ Method annotatedStaticMethod =
+ getCompanionClassOrInterface().getMethod("annotatedStaticMethod");
+ if (checkAnnotationValue(annotatedStaticMethod.getAnnotations(), 4)) {
+ System.out.println("Check 4: OK");
+ } else if (isR8()
+ && !isProguardCompatibilityMode()
+ && annotatedStaticMethod.getAnnotations().length == 0) {
+ // There is currently no way to retain annotations on static interface methods, since we
+ // drop -keep rules for such methods.
System.out.println("Check 4: OK");
} else {
System.out.println("Check 4: NOT OK");
@@ -515,4 +523,12 @@
B62168701.test();
B78901754.test();
}
+
+ public static boolean isR8() {
+ return false;
+ }
+
+ public static boolean isProguardCompatibilityMode() {
+ return false;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 1a9800a..f13a704 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -39,7 +39,7 @@
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
@@ -171,10 +171,10 @@
D8Command.Builder builder = D8Command.builder();
addClasspathReference(testJarFile, builder);
for (String inputFile : inputFiles) {
- builder = builder.addProgramFiles(Paths.get(inputFile));
+ builder.addProgramFiles(Paths.get(inputFile));
}
- for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
- builder = transformation.apply(builder);
+ for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+ transformation.accept(builder);
}
if (outputPath != null) {
builder.setOutput(outputPath, outputMode);
@@ -207,8 +207,8 @@
for (ProgramResource dexFile : dexFiles) {
builder.addDexProgramData(readResource(dexFile), dexFile.getOrigin());
}
- for (UnaryOperator<Builder> transformation : builderTransformations) {
- builder = transformation.apply(builder);
+ for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+ transformation.accept(builder);
}
if (outputPath != null) {
builder.setOutput(outputPath, outputMode);
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index 1bf86e3..b4cc992 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.OffOrAuto;
import java.nio.file.Path;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
@@ -34,8 +34,8 @@
@Override
void build(Path inputFile, Path out, OutputMode mode) throws CompilationFailedException {
D8Command.Builder builder = D8Command.builder().setOutput(out, mode);
- for (UnaryOperator<D8Command.Builder> transformation : builderTransformations) {
- builder = transformation.apply(builder);
+ for (Consumer<D8Command.Builder> transformation : builderTransformations) {
+ transformation.accept(builder);
}
builder.addLibraryFiles(
ToolHelper.getAndroidJar(
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 608f635..a92437f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -26,7 +26,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.function.UnaryOperator;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,16 +41,24 @@
"-allowaccessmodification"
);
- private static final ArrayList<String> PROGUARD_OPTIONS_N_PLUS =
- Lists.newArrayList(
- "-keepclasseswithmembers public class * {",
- " public static void main(java.lang.String[]);",
- "}",
- "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
- "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
- "-keepattributes *Annotation*",
- "-dontobfuscate",
- "-allowaccessmodification");
+ private static ArrayList<String> getProguardOptionsNPlus(
+ boolean enableProguardCompatibilityMode) {
+ return Lists.newArrayList(
+ "-keepclasseswithmembers public class * {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-keepclasseswithmembers interface **$AnnotatedInterface { <methods>; }",
+ "-neverinline interface **$AnnotatedInterface { static void annotatedStaticMethod(); }",
+ "-keepattributes *Annotation*",
+ "-dontobfuscate",
+ "-allowaccessmodification",
+ "-assumevalues class lambdadesugaringnplus.LambdasWithStaticAndDefaultMethods {",
+ " public static boolean isR8() return true;",
+ " public static boolean isProguardCompatibilityMode() return "
+ + enableProguardCompatibilityMode
+ + ";",
+ "}");
+ }
private static Map<DexVm.Version, List<String>> alsoFailsOn =
ImmutableMap.<DexVm.Version, List<String>>builder()
@@ -108,14 +116,14 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaring"))
.run();
}
@@ -147,59 +155,91 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 10, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(AndroidApiLevel.N)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaring"))
.run();
}
@Override
@Test
public void lambdaDesugaringNPlus() throws Throwable {
+ lambdaDesugaringNPlus(false);
+ }
+
+ @Test
+ public void lambdaDesugaringNPlusCompat() throws Throwable {
+ lambdaDesugaringNPlus(true);
+ }
+
+ private void lambdaDesugaringNPlus(boolean enableProguardCompatibilityMode) throws Throwable {
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withProguardCompatibilityMode(enableProguardCompatibilityMode)
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
- b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaringnplus"))
+ b ->
+ b.addProguardConfiguration(
+ getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withProguardCompatibilityMode(enableProguardCompatibilityMode)
.withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
- b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus"))
+ b ->
+ b.addProguardConfiguration(
+ getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
.run();
}
@Test
@IgnoreIfVmOlderThan(Version.V7_0_0)
public void lambdaDesugaringNPlusWithDefaultMethods() throws Throwable {
+ lambdaDesugaringNPlusWithDefaultMethods(false);
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void lambdaDesugaringNPlusWithDefaultMethodsCompat() throws Throwable {
+ lambdaDesugaringNPlusWithDefaultMethods(true);
+ }
+
+ private void lambdaDesugaringNPlusWithDefaultMethods(boolean enableProguardCompatibilityMode)
+ throws Throwable {
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withProguardCompatibilityMode(enableProguardCompatibilityMode)
.withMinApiLevel(AndroidApiLevel.N)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
- b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ b ->
+ b.addProguardConfiguration(
+ getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
.withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withProguardCompatibilityMode(enableProguardCompatibilityMode)
.withMinApiLevel(AndroidApiLevel.N)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withBuilderTransformation(ToolHelper::allowTestProguardOptions)
.withBuilderTransformation(
- b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ b ->
+ b.addProguardConfiguration(
+ getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
.withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
.run();
}
@@ -236,6 +276,8 @@
class R8TestRunner extends TestRunner<R8TestRunner> {
+ private boolean enableProguardCompatibilityMode = false;
+
R8TestRunner(String testName, String packageName, String mainClass) {
super(testName, packageName, mainClass);
}
@@ -253,11 +295,18 @@
.addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
}
+ public R8TestRunner withProguardCompatibilityMode(boolean enableProguardCompatibilityMode) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+ return this;
+ }
+
@Override
void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
- R8Command.Builder builder = R8Command.builder().setOutput(out, mode);
- for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
- builder = transformation.apply(builder);
+ CompatProguardCommandBuilder builder =
+ new CompatProguardCommandBuilder(enableProguardCompatibilityMode);
+ builder.setOutput(out, mode);
+ for (Consumer<R8Command.Builder> transformation : builderTransformations) {
+ transformation.accept(builder);
}
builder.addLibraryFiles(ToolHelper.getAndroidJar(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index bd9bd27..1dd7194 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -49,7 +49,6 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
import org.junit.Assert;
@@ -74,7 +73,7 @@
final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
- final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+ final List<Consumer<B>> builderTransformations = new ArrayList<>();
TestRunner(String testName, String packageName, String mainClass) {
this.testName = testName;
@@ -131,7 +130,6 @@
} else {
fail("Unexpected builder type: " + builder.getClass());
}
- return builder;
});
}
@@ -143,7 +141,7 @@
return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
}
- C withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+ C withBuilderTransformation(Consumer<B> builderTransformation) {
builderTransformations.add(builderTransformation);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 19c77fb..e51cf99 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1708,6 +1708,21 @@
return DescriptorUtils.javaTypeToDescriptor(typeName(clazz));
}
+ public static String methodDescriptor(Method method) {
+ return Reference.methodFromMethod(method).getMethodDescriptor();
+ }
+
+ public static String methodDescriptor(Class<?> returnType, Class<?>... parameters) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ for (Class<?> parameter : parameters) {
+ sb.append(descriptor(parameter));
+ }
+ sb.append(")");
+ sb.append(returnType == null ? "V" : descriptor(returnType));
+ return sb.toString();
+ }
+
public static PathOrigin getOrigin(Class<?> clazz) {
return new PathOrigin(ToolHelper.getClassFileForTestClass(clazz));
}
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index e3570be..fddd0d4 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -62,6 +62,10 @@
return withCfRuntimeFilter(vm -> vm == runtime);
}
+ public TestParametersBuilder withSystemRuntime() {
+ return withCfRuntimeFilter(TestParametersBuilder::isSystemJdk);
+ }
+
/** Add all available CF runtimes. */
public TestParametersBuilder withCfRuntimes() {
return withCfRuntimeFilter(vm -> true);
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 8b99ca8..a51fcf0 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -159,7 +159,7 @@
.build();
}
- public static TestRuntime getSystemRuntime() {
+ public static CfRuntime getSystemRuntime() {
String version = System.getProperty("java.version");
String home = System.getProperty("java.home");
if (version == null || version.isEmpty() || home == null || home.isEmpty()) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index c421c83..73abcee 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -351,6 +351,14 @@
return addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
}
+ public T addKeepRuntimeInvisibleAnnotations() {
+ return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_ANNOTATIONS);
+ }
+
+ public T addKeepRuntimeInvisibleParameterAnnotations() {
+ return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+ }
+
public T addKeepRuntimeVisibleAnnotations() {
return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 50f5346..f3108e5 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -165,6 +165,8 @@
public static final Path D8_JAR = Paths.get(LIBS_DIR, "d8.jar");
public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
public static final Path R8_WITH_DEPS_JAR = Paths.get(LIBS_DIR, "r8_with_deps.jar");
+ public static final Path R8_WITHOUT_DEPS_JAR =
+ Paths.get(LIBS_DIR, "r8_no_manifest_without_deps.jar");
public static final Path R8_WITH_RELOCATED_DEPS_JAR =
Paths.get(LIBS_DIR, "r8_with_relocated_deps.jar");
public static final Path R8_WITH_DEPS_11_JAR = Paths.get(LIBS_DIR, "r8_with_deps_11.jar");
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
new file mode 100644
index 0000000..bc25cd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -0,0 +1,465 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+import static org.objectweb.asm.Opcodes.AASTORE;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ANEWARRAY;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.F_SAME1;
+import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.POP;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+
+public class AndroidApiDatabaseBuilderGenerator extends TestBase {
+
+ public static String generatedMainDescriptor() {
+ return descriptor(AndroidApiDatabaseBuilderTemplate.class).replace("Template", "");
+ }
+
+ public static ClassReference ANDROID_API_LEVEL =
+ Reference.classFromBinaryName("com/android/tools/r8/utils/AndroidApiLevel");
+ public static ClassReference ANDROID_API_CLASS =
+ Reference.classFromBinaryName("com/android/tools/r8/androidapi/AndroidApiClass");
+ public static ClassReference TRAVERSAL_CONTINUATION =
+ Reference.classFromBinaryName("com/android/tools/r8/utils/TraversalContinuation");
+
+ /**
+ * Generate the classes needed for looking up api level of references in the android.jar.
+ *
+ * <p>For each api class we generate a from AndroidApiDatabaseClassTemplate, extending
+ * AndroidApiClass, such that all members can be traversed. Looking up the class reference
+ * directly in one method will generate to much code and would probably be inefficient so we first
+ * do a case on the package and then do a check on the simple name.
+ *
+ * <p>We therefore create a class file for each package, based on
+ * AndroidApiDatabasePackageTemplate, that will do the dispatch to create a new
+ * AndroidApiDatabaseClass for class in the package.
+ *
+ * <p>We then have a single entry-point, AndroidApiDatabaseBuilder, based on
+ * AndroidApiDatabaseBuilderTemplate, that will do the dispatch to AndroidApiDatabasePackage based
+ * on the package name.
+ */
+ public static void generate(List<ParsedApiClass> apiClasses, BiConsumer<String, byte[]> consumer)
+ throws Exception {
+ Map<String, List<ParsedApiClass>> packageToClassesMap = new HashMap<>();
+ List<String> packages =
+ apiClasses.stream()
+ .map(
+ apiClass -> {
+ String packageName =
+ DescriptorUtils.getPackageNameFromDescriptor(
+ apiClass.getClassReference().getDescriptor());
+ packageToClassesMap
+ .computeIfAbsent(packageName, ignoreArgument(ArrayList::new))
+ .add(apiClass);
+ return packageName;
+ })
+ .sorted()
+ .distinct()
+ .collect(Collectors.toList());
+
+ for (ParsedApiClass apiClass : apiClasses) {
+ consumer.accept(
+ getApiClassDescriptor(apiClass),
+ transformer(AndroidApiDatabaseClassTemplate.class)
+ .setClassDescriptor(getApiClassDescriptor(apiClass))
+ .addMethodTransformer(getInitTransformer(apiClass))
+ .addMethodTransformer(getApiLevelTransformer(apiClass))
+ .addMethodTransformer(getVisitFieldsTransformer(apiClass))
+ .addMethodTransformer(getVisitMethodsTransformer(apiClass))
+ .removeMethods(MethodPredicate.onName("placeHolderForInit"))
+ .removeMethods(MethodPredicate.onName("placeHolderForGetApiLevel"))
+ .removeMethods(MethodPredicate.onName("placeHolderForVisitFields"))
+ .removeMethods(MethodPredicate.onName("placeHolderForVisitMethods"))
+ .transform());
+ }
+
+ for (String pkg : packages) {
+ consumer.accept(
+ getPackageBuilderDescriptor(pkg),
+ transformer(AndroidApiDatabasePackageTemplate.class)
+ .setClassDescriptor(getPackageBuilderDescriptor(pkg))
+ .addMethodTransformer(getBuildClassTransformer(packageToClassesMap.get(pkg)))
+ .removeMethods(MethodPredicate.onName("placeHolder"))
+ .transform());
+ }
+
+ consumer.accept(
+ generatedMainDescriptor(),
+ transformer(AndroidApiDatabaseBuilderTemplate.class)
+ .setClassDescriptor(generatedMainDescriptor())
+ .addMethodTransformer(getVisitApiClassesTransformer(apiClasses))
+ .addMethodTransformer(getBuildPackageTransformer(packages))
+ .removeMethods(MethodPredicate.onName("placeHolderForVisitApiClasses"))
+ .removeMethods(MethodPredicate.onName("placeHolderForBuildClass"))
+ .transform());
+ }
+
+ private static String getPackageBuilderDescriptor(String pkg) {
+ return DescriptorUtils.javaTypeToDescriptor(
+ AndroidApiDatabasePackageTemplate.class
+ .getTypeName()
+ .replace("Template", "ForPackage_" + pkg.replace(".", "_")));
+ }
+
+ private static String getApiClassDescriptor(ParsedApiClass apiClass) {
+ return DescriptorUtils.javaTypeToDescriptor(
+ AndroidApiDatabaseClassTemplate.class
+ .getTypeName()
+ .replace(
+ "Template",
+ "ForClass_" + apiClass.getClassReference().getTypeName().replace(".", "_")));
+ }
+
+ // The transformer below changes AndroidApiDatabaseClassTemplate.<init> from:
+ // super(Reference.classFromDescriptor(placeHolderForInit()));
+ // into
+ // super(Reference.classFromDescriptor("<class-descriptor>"));
+ private static MethodTransformer getInitTransformer(ParsedApiClass apiClass) {
+ return replaceCode(
+ "placeHolderForInit",
+ transformer -> {
+ transformer.visitLdcInsn(apiClass.getClassReference().getDescriptor());
+ });
+ }
+
+ // The transformer below changes AndroidApiDatabaseClassTemplate.getApiLevel from:
+ // return placeHolderForGetApiLevel();
+ // into
+ // return AndroidApiLevel.getAndroidApiLevel(<apiLevel>);
+ private static MethodTransformer getApiLevelTransformer(ParsedApiClass apiClass) {
+ return replaceCode(
+ "placeHolderForGetApiLevel",
+ transformer -> {
+ transformer.visitLdcInsn(apiClass.getApiLevel().getLevel());
+ transformer.visitMethodInsn(
+ INVOKESTATIC,
+ ANDROID_API_LEVEL.getBinaryName(),
+ "getAndroidApiLevel",
+ "(I)" + ANDROID_API_LEVEL.getDescriptor(),
+ false);
+ });
+ }
+
+ // The transformer below changes AndroidApiDatabaseClassTemplate.visitFields from:
+ // placeHolder();
+ // return TraversalContinuation.CONTINUE;
+ // into
+ // TraversalContinuation s1 = visitField("field1", "descriptor1", apiLevel1, visitor)
+ // if (s1.shouldBreak()) {
+ // return s1;
+ // }
+ // TraversalContinuation s2 = visitField("field2", "descriptor2", apiLevel2, visitor)
+ // if (s2.shouldBreak()) {
+ // return s2;
+ // }
+ // ...
+ // return TraversalContinuation.CONTINUE;
+ private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass) {
+ return replaceCode(
+ "placeHolderForVisitFields",
+ transformer -> {
+ apiClass.visitFieldReferences(
+ (apiLevel, references) -> {
+ references.forEach(
+ reference -> {
+ transformer.visitVarInsn(ALOAD, 0);
+ transformer.visitLdcInsn(reference.getFieldName());
+ transformer.visitLdcInsn(reference.getFieldType().getDescriptor());
+ transformer.visitLdcInsn(apiLevel.getLevel());
+ transformer.visitVarInsn(ALOAD, 1);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ ANDROID_API_CLASS.getBinaryName(),
+ "visitField",
+ "(Ljava/lang/String;"
+ + "Ljava/lang/String;"
+ + "I"
+ + "Ljava/util/function/BiFunction;)"
+ + TRAVERSAL_CONTINUATION.getDescriptor(),
+ false);
+ // Note that instead of storing the result here, we dup it on the stack.
+ transformer.visitInsn(DUP);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ TRAVERSAL_CONTINUATION.getBinaryName(),
+ "shouldBreak",
+ "()Z",
+ false);
+ Label label = new Label();
+ transformer.visitJumpInsn(IFEQ, label);
+ transformer.visitInsn(ARETURN);
+ transformer.visitLabel(label);
+ transformer.visitFrame(
+ F_SAME1,
+ 0,
+ new Object[] {},
+ 1,
+ new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
+ // The pop here is needed to remove the dupped value in the case we do not
+ // return.
+ transformer.visitInsn(POP);
+ });
+ });
+ });
+ }
+
+ // The transformer below changes AndroidApiDatabaseClassTemplate.visitMethods from:
+ // placeHolderForVisitMethods();
+ // return TraversalContinuation.CONTINUE;
+ // into
+ // TraversalContinuation s1 = visitMethod(
+ // "method1", new String[] { "param11", ... , "param1X" }, null/return1, apiLevel1, visitor)
+ // if (s1.shouldBreak()) {
+ // return s1;
+ // }
+ // TraversalContinuation s1 = visitMethod(
+ // "method2", new String[] { "param21", ... , "param2X" }, null/return2, apiLevel2, visitor)
+ // if (s2.shouldBreak()) {
+ // return s2;
+ // }
+ // ...
+ // return TraversalContinuation.CONTINUE;
+ private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass) {
+ return replaceCode(
+ "placeHolderForVisitMethods",
+ transformer -> {
+ apiClass.visitMethodReferences(
+ (apiLevel, references) -> {
+ references.forEach(
+ reference -> {
+ transformer.visitVarInsn(ALOAD, 0);
+ transformer.visitLdcInsn(reference.getMethodName());
+ List<TypeReference> formalTypes = reference.getFormalTypes();
+ transformer.visitLdcInsn(formalTypes.size());
+ transformer.visitTypeInsn(ANEWARRAY, binaryName(String.class));
+ for (int i = 0; i < formalTypes.size(); i++) {
+ transformer.visitInsn(DUP);
+ transformer.visitLdcInsn(i);
+ transformer.visitLdcInsn(formalTypes.get(i).getDescriptor());
+ transformer.visitInsn(AASTORE);
+ }
+ if (reference.getReturnType() != null) {
+ transformer.visitLdcInsn(reference.getReturnType().getDescriptor());
+ } else {
+ transformer.visitInsn(ACONST_NULL);
+ }
+ transformer.visitLdcInsn(apiLevel.getLevel());
+ transformer.visitVarInsn(ALOAD, 1);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ ANDROID_API_CLASS.getBinaryName(),
+ "visitMethod",
+ "(Ljava/lang/String;"
+ + "[Ljava/lang/String;Ljava/lang/String;"
+ + "I"
+ + "Ljava/util/function/BiFunction;)"
+ + TRAVERSAL_CONTINUATION.getDescriptor(),
+ false);
+ // Note that instead of storing the result here, we dup it on the stack.
+ transformer.visitInsn(DUP);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ TRAVERSAL_CONTINUATION.getBinaryName(),
+ "shouldBreak",
+ "()Z",
+ false);
+ Label label = new Label();
+ transformer.visitJumpInsn(IFEQ, label);
+ transformer.visitInsn(ARETURN);
+ transformer.visitLabel(label);
+ transformer.visitFrame(
+ F_SAME1,
+ 0,
+ new Object[] {},
+ 1,
+ new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
+ // The pop here is needed to remove the dupped value in the case we do not
+ // return.
+ transformer.visitInsn(POP);
+ });
+ });
+ });
+ }
+
+ // The transformer below changes AndroidApiDatabasePackageTemplate.buildClass from:
+ // placeHolder();
+ // return null;
+ // into
+ // if ("<simple_class1>".equals(className)) {
+ // return new AndroidApiClassForClass_class_name1();
+ // }
+ // if ("<simple_class2>".equals(className)) {
+ // return new AndroidApiClassForClass_class_name2();
+ // }
+ // ...
+ // return null;
+ private static MethodTransformer getBuildClassTransformer(List<ParsedApiClass> classesForPackage)
+ throws NoSuchMethodException {
+ Method equals = Object.class.getMethod("equals", Object.class);
+ return replaceCode(
+ "placeHolder",
+ transformer -> {
+ classesForPackage.forEach(
+ apiClass -> {
+ transformer.visitLdcInsn(
+ DescriptorUtils.getSimpleClassNameFromDescriptor(
+ apiClass.getClassReference().getDescriptor()));
+ transformer.visitVarInsn(ALOAD, 0);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ binaryName(String.class),
+ equals.getName(),
+ methodDescriptor(equals),
+ false);
+ Label label = new Label();
+ transformer.visitJumpInsn(IFEQ, label);
+ String binaryName =
+ DescriptorUtils.getBinaryNameFromDescriptor(getApiClassDescriptor(apiClass));
+ transformer.visitTypeInsn(NEW, binaryName);
+ transformer.visitInsn(DUP);
+ transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
+ transformer.visitInsn(ARETURN);
+ transformer.visitLabel(label);
+ transformer.visitFrame(Opcodes.F_SAME, 0, new Object[] {}, 0, new Object[0]);
+ });
+ });
+ }
+
+ // The transformer below changes AndroidApiDatabaseBuilderTemplate.buildClass from:
+ // String descriptor = classReference.getDescriptor();
+ // String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+ // String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+ // placeHolderForBuildClass();
+ // return null;
+ // into
+ // String descriptor = classReference.getDescriptor();
+ // String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+ // String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+ // if ("<package_name1>".equals(packageName)) {
+ // return AndroidApiClassForPackage_package_name1(simpleClassName);
+ // }
+ // if ("<package_name2>".equals(simpleClassName)) {
+ // return AndroidApiClassForPackage_package_name2(simpleClassName);
+ // }
+ // ...
+ // return null;
+ private static MethodTransformer getBuildPackageTransformer(List<String> packages)
+ throws NoSuchMethodException {
+ Method equals = String.class.getMethod("equals", Object.class);
+ Method buildClass =
+ AndroidApiDatabasePackageTemplate.class.getMethod("buildClass", String.class);
+ return replaceCode(
+ "placeHolderForBuildClass",
+ transformer -> {
+ packages.forEach(
+ pkg -> {
+ transformer.visitLdcInsn(pkg);
+ transformer.visitVarInsn(ALOAD, 2);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ binaryName(String.class),
+ equals.getName(),
+ methodDescriptor(equals),
+ false);
+ Label label = new Label();
+ transformer.visitJumpInsn(IFEQ, label);
+ transformer.visitVarInsn(ALOAD, 3);
+ transformer.visitMethodInsn(
+ INVOKESTATIC,
+ DescriptorUtils.getBinaryNameFromDescriptor(getPackageBuilderDescriptor(pkg)),
+ buildClass.getName(),
+ methodDescriptor(buildClass),
+ false);
+ transformer.visitInsn(ARETURN);
+ transformer.visitLabel(label);
+ transformer.visitFrame(
+ Opcodes.F_FULL,
+ 4,
+ new Object[] {
+ binaryName(ClassReference.class),
+ binaryName(String.class),
+ binaryName(String.class),
+ binaryName(String.class)
+ },
+ 0,
+ new Object[0]);
+ });
+ });
+ }
+
+ // The transformer below changes AndroidApiDatabaseBuilderTemplate.buildClass from:
+ // placeHolderForVisitApiClasses();
+ // into
+ // classDescriptorConsumer.accept("<descriptor_class_1>");
+ // classDescriptorConsumer.accept("<descriptor_class_2>");
+ // ...
+ // return null;
+ private static MethodTransformer getVisitApiClassesTransformer(List<ParsedApiClass> apiClasses) {
+ return replaceCode(
+ "placeHolderForVisitApiClasses",
+ transformer -> {
+ apiClasses.forEach(
+ apiClass -> {
+ transformer.visitVarInsn(ALOAD, 0);
+ transformer.visitLdcInsn(apiClass.getClassReference().getDescriptor());
+ transformer.visitMethodInsn(
+ INVOKEINTERFACE,
+ binaryName(Consumer.class),
+ "accept",
+ "(Ljava/lang/Object;)V",
+ true);
+ });
+ });
+ }
+
+ private static MethodTransformer replaceCode(
+ String placeholderName, Consumer<MethodTransformer> consumer) {
+ return new MethodTransformer() {
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(-1, maxLocals);
+ }
+
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ if (name.equals(placeholderName)) {
+ consumer.accept(this);
+ } else {
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ }
+ };
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
new file mode 100644
index 0000000..d85e3e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -0,0 +1,343 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.AndroidApiDatabaseBuilderGenerator.generatedMainDescriptor;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestState;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
+import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidApiDatabaseBuilderGeneratorTest extends TestBase {
+
+ protected final TestParameters parameters;
+ private static final Path API_VERSIONS_XML =
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
+ private static final Path API_DATABASE_JAR =
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-database", "api-database.jar");
+ private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public AndroidApiDatabaseBuilderGeneratorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static Path generateJar() throws Exception {
+ return generateJar(
+ AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL));
+ }
+
+ private static Path generateJar(List<ParsedApiClass> apiClasses) throws Exception {
+ TemporaryFolder temp = new TemporaryFolder();
+ temp.create();
+ ZipBuilder builder = ZipBuilder.builder(temp.newFile("out.jar").toPath());
+ AndroidApiDatabaseBuilderGenerator.generate(
+ apiClasses,
+ (descriptor, content) -> {
+ try {
+ String binaryName = DescriptorUtils.getBinaryNameFromDescriptor(descriptor) + ".class";
+ builder.addBytes(binaryName, content);
+ } catch (IOException exception) {
+ throw new RuntimeException(exception);
+ }
+ });
+ return builder.build();
+ }
+
+ @Test
+ public void testCanParseApiVersionsXml() throws Exception {
+ // This tests makes a rudimentary check on the number of classes, fields and methods in
+ // api-versions.xml to ensure that the runtime tests do not vacuously succeed.
+ List<ParsedApiClass> parsedApiClasses =
+ AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+ IntBox numberOfFields = new IntBox(0);
+ IntBox numberOfMethods = new IntBox(0);
+ parsedApiClasses.forEach(
+ apiClass -> {
+ apiClass.visitFieldReferences(
+ ((apiLevel, fieldReferences) -> {
+ fieldReferences.forEach(field -> numberOfFields.increment());
+ }));
+ apiClass.visitMethodReferences(
+ ((apiLevel, methodReferences) -> {
+ methodReferences.forEach(field -> numberOfMethods.increment());
+ }));
+ });
+ // These numbers will change when updating api-versions.xml
+ assertEquals(4742, parsedApiClasses.size());
+ assertEquals(25144, numberOfFields.get());
+ assertEquals(38661, numberOfMethods.get());
+ }
+
+ @Test
+ public void testDatabaseGenerationUpToDate() throws Exception {
+ BootstrapCurrentEqualityTest.filesAreEqual(generateJar(), API_DATABASE_JAR);
+ }
+
+ /**
+ * Main entry point for building a database over references in framework to the api level they
+ * were introduced. Running main will generate a new jar and run tests on it to ensure it is
+ * compatible with R8 sources and works as expected.
+ *
+ * <p>If the generated jar passes tests it will be moved to third_party/android_jar/api-database/
+ * and override the current file in there.
+ */
+ public static void main(String[] args) throws Exception {
+ List<ParsedApiClass> parsedApiClasses =
+ AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
+ Path generatedJar = generateJar(parsedApiClasses);
+ validateJar(generatedJar, parsedApiClasses);
+ Files.move(generatedJar, API_DATABASE_JAR, REPLACE_EXISTING);
+ }
+
+ private static void validateJar(Path generated, List<ParsedApiClass> apiClasses) {
+ List<BiFunction<Path, List<ParsedApiClass>, Boolean>> tests =
+ ImmutableList.of(
+ AndroidApiDatabaseBuilderGeneratorTest::testGeneratedOutputForVisitClasses,
+ AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesContinue,
+ AndroidApiDatabaseBuilderGeneratorTest::testBuildClassesBreak,
+ AndroidApiDatabaseBuilderGeneratorTest::testNoPlaceHolder);
+ tests.forEach(
+ test -> {
+ if (!test.apply(generated, apiClasses)) {
+ throw new RuntimeException("Generated jar did not pass tests");
+ }
+ });
+ }
+
+ private static boolean testGeneratedOutputForVisitClasses(
+ Path generated, List<ParsedApiClass> parsedApiClasses) {
+ String expectedOutput =
+ StringUtils.lines(
+ ListUtils.map(
+ parsedApiClasses, apiClass -> apiClass.getClassReference().getDescriptor()));
+ return runTest(generated, TestGeneratedMainVisitClasses.class)
+ .getStdOut()
+ .equals(expectedOutput);
+ }
+
+ private static boolean testBuildClassesContinue(
+ Path generated, List<ParsedApiClass> parsedApiClasses) {
+ return runTest(generated, TestBuildClassesContinue.class)
+ .getStdOut()
+ .equals(getExpected(parsedApiClasses, false));
+ }
+
+ private static boolean testBuildClassesBreak(
+ Path generated, List<ParsedApiClass> parsedApiClasses) {
+ return runTest(generated, TestBuildClassesBreak.class)
+ .getStdOut()
+ .equals(getExpected(parsedApiClasses, true));
+ }
+
+ private static boolean testNoPlaceHolder(Path generated, List<ParsedApiClass> parsedApiClasses) {
+ try {
+ CodeInspector inspector = new CodeInspector(generated);
+ inspector
+ .allClasses()
+ .forEach(
+ clazz -> {
+ clazz.forAllMethods(
+ methods -> {
+ if (methods.getFinalName().startsWith("placeHolder")) {
+ throw new RuntimeException("Found placeHolder method in generated jar");
+ }
+ });
+ });
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ return true;
+ }
+
+ private static JvmTestRunResult runTest(Path generated, Class<?> testClass) {
+ try {
+ TemporaryFolder temporaryFolder = new TemporaryFolder();
+ temporaryFolder.create();
+ return JvmTestBuilder.create(new TestState(temporaryFolder))
+ .addProgramClassFileData(
+ transformer(testClass)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(AndroidApiDatabaseBuilderTemplate.class),
+ generatedMainDescriptor())
+ .transform())
+ .addLibraryFiles(
+ generated,
+ ToolHelper.R8_WITHOUT_DEPS_JAR,
+ getDepsWithoutGeneratedApiModelClasses(),
+ ToolHelper.getJava8RuntimeJar())
+ .run(TestRuntime.getSystemRuntime(), testClass)
+ .apply(
+ result -> {
+ if (result.getExitCode() != 0) {
+ throw new RuntimeException(result.getStdErr());
+ }
+ });
+ } catch (IOException | ExecutionException | CompilationFailedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Path getDepsWithoutGeneratedApiModelClasses() throws IOException {
+ Path tempDeps = Files.createTempDirectory("temp_deps");
+ ZipUtils.unzip(
+ ToolHelper.DEPS.toString(),
+ tempDeps.toFile(),
+ entry -> {
+ if (entry.getName().startsWith("com/android/tools/r8/apimodel/")) {
+ return false;
+ }
+ return true;
+ });
+ Path modifiedDeps = Files.createTempFile("modified_deps", ".jar");
+ ZipUtils.zip(modifiedDeps, tempDeps);
+ return modifiedDeps;
+ }
+
+ private static String getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
+ List<String> expected = new ArrayList<>();
+ parsedApiClasses.forEach(
+ apiClass -> {
+ expected.add(apiClass.getClassReference().getDescriptor());
+ expected.add(apiClass.getApiLevel().getName());
+ BooleanBox added = new BooleanBox(false);
+ apiClass.visitFieldReferences(
+ (apiLevel, fieldReferences) -> {
+ fieldReferences.forEach(
+ fieldReference -> {
+ if (added.isTrue() && abort) {
+ return;
+ }
+ added.set();
+ expected.add(fieldReference.getFieldType().getDescriptor());
+ expected.add(fieldReference.getFieldName());
+ expected.add(apiLevel.getName());
+ });
+ });
+ added.set(false);
+ apiClass.visitMethodReferences(
+ (apiLevel, methodReferences) -> {
+ methodReferences.forEach(
+ methodReference -> {
+ if (added.isTrue() && abort) {
+ return;
+ }
+ added.set();
+ expected.add(methodReference.getMethodDescriptor());
+ expected.add(methodReference.getMethodName());
+ expected.add(apiLevel.getName());
+ });
+ });
+ });
+ return StringUtils.lines(expected);
+ }
+
+ public static class TestGeneratedMainVisitClasses {
+
+ public static void main(String[] args) {
+ AndroidApiDatabaseBuilderTemplate.visitApiClasses(System.out::println);
+ }
+ }
+
+ public static class TestBuildClassesContinue {
+
+ public static void main(String[] args) {
+ AndroidApiDatabaseBuilderTemplate.visitApiClasses(
+ descriptor -> {
+ com.android.tools.r8.androidapi.AndroidApiClass apiClass =
+ AndroidApiDatabaseBuilderTemplate.buildClass(
+ Reference.classFromDescriptor(descriptor));
+ if (apiClass != null) {
+ System.out.println(descriptor);
+ System.out.println(apiClass.getApiLevel().getName());
+ apiClass.visitFields(
+ (reference, apiLevel) -> {
+ System.out.println(reference.getFieldType().getDescriptor());
+ System.out.println(reference.getFieldName());
+ System.out.println(apiLevel.getName());
+ return TraversalContinuation.CONTINUE;
+ });
+ apiClass.visitMethods(
+ (reference, apiLevel) -> {
+ System.out.println(reference.getMethodDescriptor());
+ System.out.println(reference.getMethodName());
+ System.out.println(apiLevel.getName());
+ return TraversalContinuation.CONTINUE;
+ });
+ }
+ });
+ }
+ }
+
+ public static class TestBuildClassesBreak {
+
+ public static void main(String[] args) {
+ AndroidApiDatabaseBuilderTemplate.visitApiClasses(
+ descriptor -> {
+ com.android.tools.r8.androidapi.AndroidApiClass apiClass =
+ AndroidApiDatabaseBuilderTemplate.buildClass(
+ Reference.classFromDescriptor(descriptor));
+ if (apiClass != null) {
+ System.out.println(descriptor);
+ System.out.println(apiClass.getApiLevel().getName());
+ apiClass.visitFields(
+ (reference, apiLevel) -> {
+ System.out.println(reference.getFieldType().getDescriptor());
+ System.out.println(reference.getFieldName());
+ System.out.println(apiLevel.getName());
+ return TraversalContinuation.BREAK;
+ });
+ apiClass.visitMethods(
+ (reference, apiLevel) -> {
+ System.out.println(reference.getMethodDescriptor());
+ System.out.println(reference.getMethodName());
+ System.out.println(apiLevel.getName());
+ return TraversalContinuation.BREAK;
+ });
+ }
+ });
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java
new file mode 100644
index 0000000..4b21f6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderTemplate.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.function.Consumer;
+
+/** This is a template for generating the AndroidApiDatabaseBuilder. */
+public class AndroidApiDatabaseBuilderTemplate /* AndroidApiDatabaseBuilder */ {
+
+ public static void visitApiClasses(Consumer<String> classDescriptorConsumer) {
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ placeHolderForVisitApiClasses();
+ }
+
+ public static AndroidApiClass buildClass(ClassReference classReference) {
+ String descriptor = classReference.getDescriptor();
+ String packageName = DescriptorUtils.getPackageNameFromDescriptor(descriptor);
+ String simpleClassName = DescriptorUtils.getSimpleClassNameFromDescriptor(descriptor);
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ placeHolderForBuildClass();
+ return null;
+ }
+
+ private static void placeHolderForVisitApiClasses() {}
+
+ private static void placeHolderForBuildClass() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
new file mode 100644
index 0000000..2598898
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.function.BiFunction;
+
+/** This is a template for generating AndroidApiDatabaseClass extending AndroidApiClass */
+public class AndroidApiDatabaseClassTemplate extends AndroidApiClass {
+
+ protected AndroidApiDatabaseClassTemplate() {
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ super(Reference.classFromDescriptor(placeHolderForInit()));
+ }
+
+ @Override
+ public AndroidApiLevel getApiLevel() {
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ return placeHolderForGetApiLevel();
+ }
+
+ @Override
+ public TraversalContinuation visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ placeHolderForVisitFields();
+ return TraversalContinuation.CONTINUE;
+ }
+
+ @Override
+ public TraversalContinuation visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ placeHolderForVisitMethods();
+ return TraversalContinuation.CONTINUE;
+ }
+
+ private static String placeHolderForInit() {
+ return null;
+ }
+
+ private static AndroidApiLevel placeHolderForGetApiLevel() {
+ return null;
+ }
+
+ private static void placeHolderForVisitFields() {}
+
+ private static void placeHolderForVisitMethods() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java
new file mode 100644
index 0000000..3919d11
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabasePackageTemplate.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import com.android.tools.r8.androidapi.AndroidApiClass;
+
+/** This is a template for generating the AndroidApiDatabasePackage. */
+public class AndroidApiDatabasePackageTemplate {
+
+ public static AndroidApiClass buildClass(String className) {
+ // Code added dynamically in AndroidApiDatabaseBuilderGenerator.
+ placeHolder();
+ return null;
+ }
+
+ private static void placeHolder() {}
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
new file mode 100644
index 0000000..c904dff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2021, 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.apimodel;
+
+import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.BiConsumer;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class AndroidApiVersionsXmlParser {
+
+ private final List<ParsedApiClass> classes = new ArrayList<>();
+
+ private final File apiVersionsXml;
+ private final AndroidApiLevel maxApiLevel;
+
+ private AndroidApiVersionsXmlParser(File apiVersionsXml, AndroidApiLevel maxApiLevel) {
+ this.apiVersionsXml = apiVersionsXml;
+ this.maxApiLevel = maxApiLevel;
+ }
+
+ private ParsedApiClass register(ClassReference reference, AndroidApiLevel apiLevel) {
+ ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel);
+ classes.add(parsedApiClass);
+ return parsedApiClass;
+ }
+
+ private void readApiVersionsXmlFile() throws Exception {
+ CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(maxApiLevel));
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Document document = factory.newDocumentBuilder().parse(apiVersionsXml);
+ NodeList classes = document.getElementsByTagName("class");
+ for (int i = 0; i < classes.getLength(); i++) {
+ Node node = classes.item(i);
+ assert node.getNodeType() == Node.ELEMENT_NODE;
+ AndroidApiLevel apiLevel = getMaxAndroidApiLevelFromNode(node, AndroidApiLevel.B);
+ String type = DescriptorUtils.getJavaTypeFromBinaryName(getName(node));
+ ClassSubject clazz = inspector.clazz(type);
+ if (!clazz.isPresent()) {
+ // TODO(b/190326408): Investigate why the class is not present.
+ continue;
+ }
+ ClassReference originalReference = clazz.getOriginalReference();
+ ParsedApiClass parsedApiClass = register(originalReference, apiLevel);
+ NodeList members = node.getChildNodes();
+ for (int j = 0; j < members.getLength(); j++) {
+ Node memberNode = members.item(j);
+ if (isMethod(memberNode)) {
+ // TODO(b/190326408): Check for existence.
+ parsedApiClass.register(
+ getMethodReference(originalReference, memberNode),
+ getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
+ } else if (isField(memberNode)) {
+ // The field do not have descriptors and are supposed to be unique.
+ FieldSubject fieldSubject = clazz.uniqueFieldWithName(getName(memberNode));
+ if (!fieldSubject.isPresent()) {
+ // TODO(b/190326408): Investigate why the member is not present.
+ continue;
+ }
+ parsedApiClass.register(
+ fieldSubject.getOriginalReference(),
+ getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
+ }
+ }
+ }
+ }
+
+ private boolean isMethod(Node node) {
+ return node.getNodeName().equals("method");
+ }
+
+ private String getName(Node node) {
+ return node.getAttributes().getNamedItem("name").getNodeValue();
+ }
+
+ private MethodReference getMethodReference(ClassReference classDescriptor, Node node) {
+ assert isMethod(node);
+ String name = getName(node);
+ int signatureStart = name.indexOf('(');
+ assert signatureStart > 0;
+ String parsedName = name.substring(0, signatureStart).replace("<", "<");
+ assert !parsedName.contains("&");
+ return Reference.methodFromDescriptor(
+ classDescriptor.getDescriptor(), parsedName, name.substring(signatureStart));
+ }
+
+ private boolean isField(Node node) {
+ return node.getNodeName().equals("field");
+ }
+
+ private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
+ if (node == null) {
+ return defaultValue;
+ }
+ Node since = node.getAttributes().getNamedItem("since");
+ if (since == null) {
+ return defaultValue;
+ }
+ return defaultValue.max(
+ AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue())));
+ }
+
+ public static List<ParsedApiClass> getParsedApiClasses(
+ File apiVersionsXml, AndroidApiLevel apiLevel) throws Exception {
+ AndroidApiVersionsXmlParser parser = new AndroidApiVersionsXmlParser(apiVersionsXml, apiLevel);
+ parser.readApiVersionsXmlFile();
+ return parser.classes;
+ }
+
+ public static class ParsedApiClass {
+
+ private final ClassReference classReference;
+ private final AndroidApiLevel apiLevel;
+ private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>();
+ private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>();
+
+ public ClassReference getClassReference() {
+ return classReference;
+ }
+
+ public AndroidApiLevel getApiLevel() {
+ return apiLevel;
+ }
+
+ private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) {
+ this.classReference = classReference;
+ this.apiLevel = apiLevel;
+ }
+
+ private void register(FieldReference reference, AndroidApiLevel apiLevel) {
+ fieldReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
+ }
+
+ private void register(MethodReference reference, AndroidApiLevel apiLevel) {
+ methodReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
+ }
+
+ public void visitFieldReferences(BiConsumer<AndroidApiLevel, List<FieldReference>> consumer) {
+ fieldReferences.forEach(
+ (apiLevel, references) -> {
+ references.sort(Comparator.comparing(FieldReference::getFieldName));
+ consumer.accept(apiLevel, references);
+ });
+ }
+
+ public void visitMethodReferences(BiConsumer<AndroidApiLevel, List<MethodReference>> consumer) {
+ methodReferences.forEach(
+ (apiLevel, references) -> {
+ references.sort(Comparator.comparing(MethodReference::getMethodName));
+ consumer.accept(apiLevel, references);
+ });
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
index 8045b48..5d0c0b8 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -2,9 +2,9 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -74,7 +74,7 @@
// a method reference to `Api` and only because of the type reference in the field `api`.
Class<?> aClass =
Class.forName(
- "com.android.tools.r8.apimodeling.ApiModelClassMergingWithDifferentApiFieldsTest_Api"
+ "com.android.tools.r8.apimodel.ApiModelClassMergingWithDifferentApiFieldsTest_Api"
.replace("_", "$"));
Method foo = aClass.getDeclaredMethod("foo");
foo.invoke(api);
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
index b56d6ae..f174546 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingWithDifferentApiMethodsTest.java
@@ -2,9 +2,9 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
similarity index 77%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
index 2e24790..f6996e9 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -2,10 +2,13 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
@@ -34,9 +37,8 @@
@Test
public void testR8() throws Exception {
- Method apiCallerApiLevel1 = ApiCaller.class.getDeclaredMethod("apiLevel22");
- Method apiCallerCallerApiLevel1 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
- Method otherCallerApiLevel1 = OtherCaller.class.getDeclaredMethod("apiLevel1");
+ Method apiCallerApiLevel22 = ApiCaller.class.getDeclaredMethod("apiLevel22");
+ Method apiCallerCallerApiLevel22 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
testForR8(parameters.getBackend())
.addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
.addLibraryClasses(ApiType.class)
@@ -50,22 +52,21 @@
.addRunClasspathClasses(ApiType.class)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(ApiType.class.getName())
- .inspect(verifyThat(parameters, apiCallerApiLevel1).inlinedInto(apiCallerCallerApiLevel1))
- // TODO(b/138781768): Should not be inlined
- .inspect(
- verifyThat(parameters, apiCallerCallerApiLevel1).inlinedInto(otherCallerApiLevel1));
+ .inspect(verifyThat(parameters, apiCallerApiLevel22).inlinedInto(apiCallerCallerApiLevel22))
+ .inspect(inspector -> assertThat(inspector.clazz(OtherCaller.class), not(isPresent())));
}
public static class ApiType {}
@NoHorizontalClassMerging
public static class ApiCaller {
+
public static ApiType apiLevel22() throws Exception {
// The reflective call here is to ensure that the setting of A's api level is not based on
// a method reference to `Api` and only because of the type reference in the field `api`.
Class<?> reflectiveCall =
Class.forName(
- "com.android.tools.r8.apimodeling.ApiModelInlineMethodWithApiTypeTest_ApiType"
+ "com.android.tools.r8.apimodel.ApiModelInlineMethodWithApiTypeTest_ApiType"
.replace("_", "$"));
return (ApiType) reflectiveCall.getDeclaredConstructor().newInstance();
}
@@ -81,6 +82,7 @@
}
}
+ @NoHorizontalClassMerging
public static class OtherCaller {
public static void apiLevel1() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
index 3bd5b8a..777b250 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningFieldTest.java
@@ -2,9 +2,9 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningMethodTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningMethodTest.java
index a7804e0..ac42465 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoClassInliningMethodTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
index f210e6f..c7b2b1c 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -50,8 +50,9 @@
.apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
.compile()
- // TODO(b/138781768): Should not be inlined
- .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
.addRunClasspathClasses(Api.class)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 4e5c39d..602f4a1 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
similarity index 91%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
index 048874d..e79cfed 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
index 0087275..28a2b00 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
@@ -50,8 +50,9 @@
.apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
.compile()
- // TODO(b/138781768): Should not be inlined
- .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
.addRunClasspathClasses(Api.class)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticTest.java
index dfb8628..b5c7134 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelStaticTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
index 415a163..7d6230b 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -2,10 +2,10 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
similarity index 89%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
index 31af9e3..d87ed82 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
@@ -2,11 +2,11 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.apimodel.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
index a04774b..80e213e 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVerticalMergingOfSuperClassTest.java
@@ -2,9 +2,9 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
-import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForType;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
rename to src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index b20e395..33d1deb 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -2,7 +2,7 @@
// 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.apimodeling;
+package com.android.tools.r8.apimodel;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
new file mode 100644
index 0000000..fadf934
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, 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.classmerging;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+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 StatefulSingletonClassesMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StatefulSingletonClassesMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .noClassStaticizing()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ A.INSTANCE.f();
+ B.INSTANCE.g();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ static final A INSTANCE = new A("A");
+
+ @NeverPropagateValue private final String data;
+
+ A(String data) {
+ this.data = data;
+ }
+
+ @NeverInline
+ void f() {
+ System.out.println(data);
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ static final B INSTANCE = new B("B");
+
+ @NeverPropagateValue private final String data;
+
+ B(String data) {
+ this.data = data;
+ }
+
+ @NeverInline
+ void g() {
+ System.out.println(data);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
index e063a4b..d784a77 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/DistinguishExceptionClassesTest.java
@@ -8,6 +8,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class DistinguishExceptionClassesTest extends HorizontalClassMergingTestBase {
@@ -20,6 +21,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ HorizontallyMergedClassesInspector::assertNoClassesMerged)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("test success")
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingWithSafeCheckCastTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingWithSafeCheckCastTest.java
new file mode 100644
index 0000000..02f0f5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingWithSafeCheckCastTest.java
@@ -0,0 +1,125 @@
+// 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.classmerging.horizontal;
+
+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.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class MergingWithSafeCheckCastTest extends HorizontalClassMergingTestBase {
+
+ public MergingWithSafeCheckCastTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(A.class, B.class)
+ .assertIsCompleteMergeGroup(I.class, J.class)
+ .assertNoOtherClassesMerged())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ // Check that the field f has been changed to have type java.lang.Object.
+ assertEquals(1, aClassSubject.allFields().size());
+ assertEquals(
+ Object.class.getTypeName(),
+ aClassSubject.allFields().get(0).getField().getType().getTypeName());
+
+ // Check that casts have been inserted into main().
+ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+ assertTrue(
+ mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isCheckCast));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("I", "J");
+ }
+
+ public static class Main {
+ public static void main(String[] args) {
+ new A(new IImpl()).f.printI();
+ new B(new JImpl()).f.printJ();
+ }
+ }
+
+ @NeverClassInline
+ public static class A {
+
+ @NeverPropagateValue I f;
+
+ A(I f) {
+ this.f = f;
+ }
+ }
+
+ @NeverClassInline
+ public static class B {
+
+ @NeverPropagateValue J f;
+
+ B(J f) {
+ this.f = f;
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+ void printI();
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J {
+ void printJ();
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class IImpl implements I {
+ @NeverInline
+ public void printI() {
+ System.out.println("I");
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class JImpl implements J {
+ @NeverInline
+ public void printJ() {
+ System.out.println("J");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
index 385a3fe..3858a1e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoClassesOrMembersWithAnnotationsTest.java
@@ -6,45 +6,72 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.List;
import org.junit.Test;
+import org.junit.runners.Parameterized;
public class NoClassesOrMembersWithAnnotationsTest extends HorizontalClassMergingTestBase {
- public NoClassesOrMembersWithAnnotationsTest(TestParameters parameters) {
+ private final boolean enableProguardCompatibilityMode;
+
+ @Parameterized.Parameters(name = "{1}, compat: {0}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public NoClassesOrMembersWithAnnotationsTest(
+ boolean enableProguardCompatibilityMode, TestParameters parameters) {
super(parameters);
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
}
@Test
public void testR8() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.addHorizontallyMergedClassesInspector(
- inspector -> inspector.assertIsCompleteMergeGroup(A.class, C.class))
+ inspector -> {
+ if (enableProguardCompatibilityMode) {
+ inspector.assertIsCompleteMergeGroup(A.class, C.class);
+ } else {
+ inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class);
+ }
+ })
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines(
- "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2")
+ .applyIf(
+ enableProguardCompatibilityMode,
+ result ->
+ result.assertSuccessWithOutputLines(
+ "a", "b", "c", "foo", "null", "annotation 2", "annotation 1", "annotation 2"),
+ result ->
+ result.assertSuccessWithOutputLines("a", "b", "c", "foo", "null", "annotation 2"))
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(TypeAnnotation.class), isPresent());
assertThat(codeInspector.clazz(MethodAnnotation.class), isPresent());
assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B.class),
+ onlyIf(enableProguardCompatibilityMode, isPresent()));
assertThat(codeInspector.clazz(C.class), isAbsent());
});
}
@@ -110,14 +137,18 @@
return null;
}
});
- System.out.println(
- b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
- System.out.println(
- c.getClass()
- .getMethods()[0]
- .getAnnotations()[0]
- .toString()
- .replaceFirst(".*", "annotation 2"));
+ if (b.getClass().getAnnotations().length > 0) {
+ System.out.println(
+ b.getClass().getAnnotations()[0].toString().replaceFirst(".*", "annotation 1"));
+ }
+ if (c.getClass().getMethods()[0].getAnnotations().length > 0) {
+ System.out.println(
+ c.getClass()
+ .getMethods()[0]
+ .getAnnotations()[0]
+ .toString()
+ .replaceFirst(".*", "annotation 2"));
+ }
}
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
index 795837e..c3a7cfc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingWithNonVisibleAnnotationTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.classmerging.vertical;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -41,7 +40,7 @@
@Test
public void testR8() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(VerticalClassMergingWithNonVisibleAnnotationTestClasses.class)
.addProgramClasses(Sub.class)
.setMinApi(parameters.getApiLevel())
@@ -50,14 +49,14 @@
VerticalClassMergingWithNonVisibleAnnotationTestClasses.class.getTypeName()
+ "$Private* { *; }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(Base.class))
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
.run(parameters.getRuntime(), Sub.class)
.assertSuccessWithOutputLines("Base::foo()", "Sub::bar()")
.inspect(
codeInspector -> {
- // Assert that base has been vertically merged into Sub.
- assertThat(codeInspector.clazz(Base.class), not(isPresent()));
ClassSubject sub = codeInspector.clazz(Sub.class);
assertThat(sub, isPresent());
// Assert that the merged class has no annotations from Base
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
new file mode 100644
index 0000000..e705ca6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -0,0 +1,259 @@
+// Copyright (c) 2021, 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.desugaredlibrary;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.util.Collection;
+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 DesugaredLibraryMismatchTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final AndroidApiLevel apiLevel;
+
+ @Parameters(name = "API level: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withNoneRuntime().build(),
+ new AndroidApiLevel[] {
+ AndroidApiLevel.LATEST, AndroidApiLevel.O, AndroidApiLevel.N_MR1, AndroidApiLevel.B
+ });
+ }
+
+ public DesugaredLibraryMismatchTest(TestParameters parameters, AndroidApiLevel apiLevel) {
+ this.parameters = parameters;
+ this.apiLevel = apiLevel;
+ }
+
+ @Test
+ public void testInputDexed() throws Exception {
+ // DEX code without library desugaring.
+ Path libraryDex =
+ testForD8(Backend.DEX)
+ .addProgramClasses(Library.class)
+ .setMinApi(apiLevel)
+ .compile()
+ .writeToZip();
+
+ // Combine DEX input without library desugaring with dexing with library desugaring.
+ try {
+ testForD8()
+ .addProgramFiles(libraryDex)
+ .addProgramClasses(TestRunner.class)
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(apiLevel)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertNoInfos();
+ diagnostics.assertAllWarningsMatch(
+ diagnosticMessage(
+ containsString(
+ "The compilation is slowed down due to a mix of class file and dex"
+ + " file inputs in the context of desugared library.")));
+ if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+ diagnostics.assertErrorsMatch(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ });
+
+ } catch (CompilationFailedException e) {
+ }
+ }
+
+ @Test
+ public void testInputCfDesugared() throws Exception {
+ // CF to CF desugared code without library desugaring.
+ Path desugaredLibrary =
+ testForD8(Backend.CF)
+ .addProgramClasses(Library.class)
+ .setMinApi(apiLevel)
+ .compile()
+ .writeToZip();
+
+ // Combine CF desugared input without library desugaring with dexing with library desugaring.
+ testForD8()
+ .addProgramFiles(desugaredLibrary)
+ .addProgramClasses(TestRunner.class)
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(apiLevel)
+ .compile();
+ }
+
+ @Test
+ public void testInputCfDesugaredAndDexed() throws Exception {
+ // CF to CF desugared code without library desugaring.
+ Path desugaredLibrary =
+ testForD8(Backend.CF)
+ .addProgramClasses(Library.class)
+ .setMinApi(apiLevel)
+ .compile()
+ .writeToZip();
+
+ // CF to CF desugared code without library desugaring compiled to DEX.
+ Path desugaredLibraryDex =
+ testForD8(Backend.DEX)
+ .addProgramFiles(desugaredLibrary)
+ .setMinApi(apiLevel)
+ .disableDesugaring()
+ .compile()
+ .writeToZip();
+
+ testForD8()
+ .addProgramFiles(desugaredLibraryDex)
+ .addProgramClasses(TestRunner.class)
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(apiLevel)
+ .compile();
+ }
+
+ @Test
+ public void testCfInputLibraryDesugared() throws Exception {
+ // CF to CF desugared code with library desugaring.
+ Path desugaredLibrary =
+ testForD8(Backend.CF)
+ .addProgramClasses(Library.class)
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(apiLevel)
+ .compile()
+ .writeToZip();
+
+ // Combine CF input with library desugaring with dexing without library desugaring.
+ try {
+ testForD8()
+ .addProgramFiles(desugaredLibrary)
+ .addProgramClasses(TestRunner.class)
+ .setMinApi(apiLevel)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ });
+ } catch (CompilationFailedException e) {
+ }
+ }
+
+ @Test
+ public void testMergeLibraryDesugaredWithNotLibraryDesugared() throws Exception {
+ // DEX code with library desugaring.
+ Path libraryDex =
+ testForD8(Backend.DEX)
+ .addProgramClasses(Library.class)
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(apiLevel)
+ .compile()
+ .writeToZip();
+
+ // DEX code without library desugaring.
+ Path programDex =
+ testForD8(Backend.DEX)
+ .addProgramClasses(TestRunner.class)
+ .setMinApi(apiLevel)
+ .compile()
+ .writeToZip();
+
+ try {
+ testForD8()
+ .addProgramFiles(libraryDex)
+ .addProgramFiles(programDex)
+ .setMinApi(apiLevel)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ });
+ } catch (CompilationFailedException e) {
+ }
+ }
+
+ @Test
+ public void testMergeDifferentLibraryDesugarVersions() throws Exception {
+ // DEX code with library desugaring using a desugared library configuration with a
+ // different identifier.
+ String identifier = "my-identifier";
+ Path libraryDex =
+ testForD8(Backend.DEX)
+ .applyIf(
+ apiLevel.isLessThan(AndroidApiLevel.O),
+ builder ->
+ builder.addOptionsModification(
+ options ->
+ options.desugaredLibraryConfiguration =
+ DesugaredLibraryConfiguration.builder(
+ options.dexItemFactory(),
+ options.reporter,
+ Origin.unknown())
+ .setDesugaredLibraryIdentifier(identifier)
+ .build()))
+ .addProgramClasses(Library.class)
+ .setMinApi(apiLevel)
+ .enableCoreLibraryDesugaring(apiLevel)
+ .compile()
+ .writeToZip();
+
+ // DEX code without library desugaring.
+ Path programDex =
+ testForD8(Backend.DEX)
+ .addProgramClasses(TestRunner.class)
+ .setMinApi(apiLevel)
+ .compile()
+ .writeToZip();
+
+ try {
+ testForD8()
+ .addProgramFiles(libraryDex)
+ .addProgramFiles(programDex)
+ .setMinApi(apiLevel)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (apiLevel.isLessThan(AndroidApiLevel.O)) {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticType(DesugaredLibraryMismatchDiagnostic.class),
+ diagnosticMessage(containsString(identifier))));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ });
+ } catch (CompilationFailedException e) {
+ }
+ }
+
+ static class Library {}
+
+ static class TestRunner {
+
+ public static void main(String[] args) {
+ System.out.println(Library.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index 7c46fe3..be9ae1a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -160,31 +160,27 @@
// Build an app using library desugaring merging with library not using library desugaring.
Path app;
- try {
- app =
- testForD8()
- .addProgramFiles(buildPart1DesugaredLibrary(), desugaredLibDex)
- .setMinApi(parameters.getApiLevel())
- .compile()
- .writeToZip();
+ app =
+ testForD8()
+ .addProgramFiles(buildPart1DesugaredLibrary(), desugaredLibDex)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
- // When there is no class-file resources we are adding the marker for the last compilation.
- assertMarkersMatch(
- ExtractMarker.extractMarkerFromDexFile(app),
- ImmutableList.of(
- markerMatcher,
- allOf(
- markerTool(Tool.D8),
- markerCompilationMode(CompilationMode.DEBUG),
- markerBackend(Backend.DEX),
- markerIsDesugared(),
- markerMinApi(parameters.getApiLevel()),
- not(markerHasDesugaredLibraryIdentifier()))));
- } catch (CompilationFailedException e) {
- assertTrue(someLibraryDesugaringRequired());
- return;
- }
- assert !someLibraryDesugaringRequired();
+ // When there is no class-file resources we are adding the marker for the last compilation.
+ assertMarkersMatch(
+ ExtractMarker.extractMarkerFromDexFile(app),
+ ImmutableList.of(
+ markerMatcher,
+ allOf(
+ markerTool(Tool.D8),
+ markerCompilationMode(CompilationMode.DEBUG),
+ markerBackend(Backend.DEX),
+ markerIsDesugared(),
+ markerMinApi(parameters.getApiLevel()),
+ someLibraryDesugaringRequired()
+ ? markerHasDesugaredLibraryIdentifier()
+ : not(markerHasDesugaredLibraryIdentifier()))));
}
private void assertError(TestDiagnosticMessages m) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SimpleStreamTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SimpleStreamTest.java
new file mode 100644
index 0000000..aec1045
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SimpleStreamTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2021, 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+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 SimpleStreamTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT = StringUtils.lines("3");
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public SimpleStreamTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testStreamD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(SimpleStreamTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testStreamR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addInnerClasses(SimpleStreamTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(Executor.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @SuppressWarnings("unchecked")
+ static class Executor {
+
+ public static void main(String[] args) {
+ ArrayList<Integer> integers = new ArrayList<>();
+ integers.add(1);
+ integers.add(2);
+ integers.add(3);
+ List<Integer> collectedList = integers.stream().map(i -> i + 3).collect(Collectors.toList());
+ System.out.println(collectedList.size());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
index 7d70d21..43373b9 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/MethodParametersTest.java
@@ -5,18 +5,18 @@
import static com.android.tools.r8.TestRuntime.getCheckedInJdk;
import static com.android.tools.r8.TestRuntime.getCheckedInJdk11;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugaring.interfacemethods.methodparameters.I;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.SupplierUtils;
import java.nio.file.Path;
+import java.util.function.Supplier;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -25,6 +25,7 @@
public class MethodParametersTest extends TestBase {
private final TestParameters parameters;
+ private final Supplier<Path> compiledWithParameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
@@ -38,16 +39,46 @@
public MethodParametersTest(TestParameters parameters) {
this.parameters = parameters;
+ compiledWithParameters =
+ SupplierUtils.memoize(
+ () ->
+ javac(
+ parameters.isCfRuntime()
+ ? getCheckedInJdk(parameters.getRuntime().asCf().getVm())
+ : getCheckedInJdk11())
+ .addSourceFiles(ToolHelper.getSourceFileForTestClass(I.class))
+ .addOptions("-parameters")
+ .compile());
}
+ private final String EXPECTED =
+ StringUtils.lines(
+ "0", "1", "a: 1", "2", "a: 1", "b: 2", "0", "1", "a: 1", "2", "a: 1", "b: 2");
+ private final String EXPECTED_DESUGARED =
+ StringUtils.lines(
+ "1",
+ "2",
+ "_this: 0",
+ "a: 1",
+ "3",
+ "_this: 0",
+ "a: 1",
+ "b: 2",
+ "0",
+ "1",
+ "a: 1",
+ "2",
+ "a: 1",
+ "b: 2");
+
@Test
public void testJvm() throws Exception {
assumeTrue(parameters.isCfRuntime());
testForJvm()
- .addProgramClassesAndInnerClasses(I.class)
+ .addProgramFiles(compiledWithParameters.get())
.addInnerClasses(getClass())
.run(parameters.getRuntime(), TestRunner.class)
- .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2");
+ .assertSuccessWithOutput(EXPECTED);
}
@Test
@@ -56,18 +87,9 @@
assumeTrue(
parameters.isDexRuntime()
|| getCheckedInJdk(parameters.getRuntime().asCf().getVm()) != null);
- Path compiledWithParameters =
- javac(
- parameters.isCfRuntime()
- ? getCheckedInJdk(parameters.getRuntime().asCf().getVm())
- : getCheckedInJdk11())
- .addSourceFiles(ToolHelper.getSourceFileForTestClass(I.class))
- .addOptions("-parameters")
- .compile();
-
Path interfaceDesugared =
testForD8(Backend.CF)
- .addProgramFiles(compiledWithParameters)
+ .addProgramFiles(compiledWithParameters.get())
.setMinApi(parameters.getApiLevel())
.compile()
.writeToZip();
@@ -77,17 +99,6 @@
.addProgramFiles(interfaceDesugared)
.setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/189743726): These warnings should not be there.
- .applyIf(
- parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
- TestCompileResult::assertNoInfoMessages,
- r ->
- r.assertAtLeastOneInfoMessage()
- .assertAllInfoMessagesMatch(
- anyOf(
- containsString(
- "Invalid parameter counts in MethodParameter attributes"),
- containsString("Methods with invalid MethodParameter attributes"))))
.writeToZip();
Path programDesugared =
@@ -103,26 +114,11 @@
.addProgramFiles(programDesugared)
.setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/189743726): These warnings should not be there.
- .applyIf(
- parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
- TestCompileResult::assertNoInfoMessages,
- r ->
- r.assertAtLeastOneInfoMessage()
- .assertAllInfoMessagesMatch(
- anyOf(
- containsString(
- "Invalid parameter counts in MethodParameter attributes"),
- containsString("Methods with invalid MethodParameter attributes"))))
.run(parameters.getRuntime(), TestRunner.class)
.applyIf(
parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring(),
- r -> r.assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2"),
- // TODO(b/189743726): Should not fail at runtime (but will have different parameter
- // count for non-static methods when desugared).
- r ->
- r.assertFailureWithErrorThatMatches(
- containsString("Wrong number of parameters in MethodParameters attribute")));
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r -> r.assertSuccessWithOutput(EXPECTED_DESUGARED));
}
static class A implements I {}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
index 4c7ba69..ccf787e 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/methodparameters/I.java
@@ -3,29 +3,67 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugaring.interfacemethods.methodparameters;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Parameter;
+
public interface I {
default void zeroArgsDefault() {
System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
}
- default void oneArgDefault(int a) {
- System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ default void oneArgDefault(@RuntimeAnnotation1(n = 0) int a) {
+ Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+ System.out.println(parameters.length);
+ for (Parameter parameter : parameters) {
+ System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+ }
}
- default void twoArgDefault(int a, int b) {
- System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ default void twoArgDefault(
+ @RuntimeAnnotation1(n = 1) int a,
+ @RuntimeAnnotation1(n = 2) @RuntimeAnnotation2(n = 2) int b) {
+ Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+ System.out.println(parameters.length);
+ for (Parameter parameter : parameters) {
+ System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+ }
}
static void zeroArgStatic() {
- System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+ System.out.println(parameters.length);
+ for (Parameter parameter : parameters) {
+ System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+ }
}
- static void oneArgStatic(int a) {
- System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ static void oneArgStatic(@RuntimeAnnotation1(n = 0) int a) {
+ Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+ System.out.println(parameters.length);
+ for (Parameter parameter : parameters) {
+ System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+ }
}
- static void twoArgsStatic(int a, int b) {
- System.out.println(new Object() {}.getClass().getEnclosingMethod().getParameters().length);
+ static void twoArgsStatic(
+ @RuntimeAnnotation1(n = 1) int a,
+ @RuntimeAnnotation1(n = 2) @RuntimeAnnotation2(n = 2) int b) {
+ Parameter[] parameters = new Object() {}.getClass().getEnclosingMethod().getParameters();
+ System.out.println(parameters.length);
+ for (Parameter parameter : parameters) {
+ System.out.println(parameter.getName() + ": " + parameter.getAnnotations().length);
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface RuntimeAnnotation1 {
+ int n();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface RuntimeAnnotation2 {
+ int n();
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 6382436..c6094ae 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -42,9 +42,8 @@
Assume.assumeFalse(
"The methods values and valueOf are required for reflection.",
enumKeepRules.toString().equals("none"));
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(AnnotationEnumUnboxingTest.class)
- .noMinification()
.addKeepMainRule(Main.class)
.addKeepRules(enumKeepRules.getKeepRules())
.addKeepClassRules(ClassAnnotationDefault.class)
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index 0326e0b..e7b5dce 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -8,9 +8,8 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.enumunboxing.DoubleProcessingEnumUnboxingTest.App.AppEnum;
import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -78,19 +77,21 @@
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::assertUtilityClassPresent)
+ .inspect(this::assertSharedUtilityClassPresent)
.run(parameters.getRuntime(), App.class)
.assertSuccess()
.inspectStdOut(this::assertLines2By2Correct);
}
- private void assertUtilityClassPresent(CodeInspector codeInspector) {
+ private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
assertTrue(
codeInspector.allClasses().stream()
.anyMatch(
c ->
c.getOriginalName()
- .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+ .contains(
+ SharedEnumUnboxingUtilityClass
+ .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
}
static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index 44cdff0..4ef092d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -68,7 +68,7 @@
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::assertUtilityClassPresent)
+ .inspect(this::assertSharedUtilityClassPresent)
.run(parameters.getRuntime(), App.class)
.assertSuccess()
.inspectStdOut(this::assertLines2By2Correct);
@@ -88,13 +88,15 @@
.writeToZip();
}
- private void assertUtilityClassPresent(CodeInspector codeInspector) {
+ private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
assertTrue(
codeInspector.allClasses().stream()
.anyMatch(
c ->
c.getOriginalName()
- .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+ .contains(
+ SharedEnumUnboxingUtilityClass
+ .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
}
static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java
new file mode 100644
index 0000000..82c48eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/RedundantValuesCloneEnumUnboxingTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2021, 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 static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantValuesCloneEnumUnboxingTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ // Verify that there are no calls to clone().
+ .inspect(
+ inspector ->
+ inspector.forAllClasses(
+ clazz ->
+ clazz.forAllMethods(
+ method -> assertThat(method, not(invokesMethodWithName("clone"))))))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ for (MyEnum e : MyEnum.values()) {
+ System.out.println(e.name());
+ }
+ }
+ }
+
+ enum MyEnum {
+ A
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
index 904fc48f..2211d08 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
@@ -59,12 +59,76 @@
public static void main(String[] args) {
System.out.println(MyEnum.A.ordinal());
System.out.println(0);
+ stringValueOf();
+ stringBuilder();
+ }
+
+ private static void stringValueOf() {
System.out.println(getString(MyEnum.A));
System.out.println("A");
System.out.println(getString(null));
System.out.println("null");
}
+ private static void stringBuilder() {
+ StringBuilder stringBuilder = new StringBuilder();
+ append(stringBuilder, MyEnum.A);
+ append(stringBuilder, MyEnum.B);
+ append(stringBuilder, null);
+ appendTryCatch(stringBuilder, MyEnum.A);
+ appendTryCatch(stringBuilder, MyEnum.B);
+ appendTryCatch(stringBuilder, null);
+ System.out.println(stringBuilder.toString());
+ System.out.println("ABnullABnull");
+
+ StringBuffer stringBuffer = new StringBuffer();
+ append(stringBuffer, MyEnum.A);
+ append(stringBuffer, MyEnum.B);
+ append(stringBuffer, null);
+ appendTryCatch(stringBuffer, MyEnum.A);
+ appendTryCatch(stringBuffer, MyEnum.B);
+ appendTryCatch(stringBuffer, null);
+ System.out.println(stringBuffer.toString());
+ System.out.println("ABnullABnull");
+ }
+
+ @NeverInline
+ private static StringBuilder append(StringBuilder sb, MyEnum e) {
+ return sb.append(e);
+ }
+
+ @NeverInline
+ private static StringBuffer append(StringBuffer sb, MyEnum e) {
+ return sb.append(e);
+ }
+
+ @NeverInline
+ private static StringBuilder appendTryCatch(StringBuilder sb, MyEnum e) {
+ try {
+ sb.append(e);
+ throwNull();
+ } catch (NullPointerException ignored) {
+ }
+ return sb;
+ }
+
+ @NeverInline
+ private static StringBuffer appendTryCatch(StringBuffer sb, MyEnum e) {
+ try {
+ sb.append(e);
+ throwNull();
+ } catch (NullPointerException ignored) {
+ }
+ return sb;
+ }
+
+ @NeverInline
+ private static void throwNull() {
+ if (System.currentTimeMillis() > 0) {
+ throw new NullPointerException("exception");
+ }
+ }
+
@NeverInline
private static String getString(MyEnum e) {
return String.valueOf(e);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
new file mode 100644
index 0000000..9e42eb6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationPinnedMethodOverridePropagationTest.java
@@ -0,0 +1,166 @@
+// 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.callsites;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 CallSiteOptimizationPinnedMethodOverridePropagationTest extends TestBase {
+
+ private static final String CLASS_PREFIX =
+ "com.android.tools.r8.ir.optimize.callsites.CallSiteOptimizationPinnedMethodOverridePropagationTest$";
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public CallSiteOptimizationPinnedMethodOverridePropagationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ R8TestCompileResult compiled =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(
+ Arg.class, Arg1.class, Arg2.class, Call.class, CallImpl.class, Main2.class)
+ .addKeepRules(
+ ImmutableList.of(
+ "-keep interface " + CLASS_PREFIX + "Arg",
+ "-keep interface "
+ + CLASS_PREFIX
+ + "Call { \npublic void print("
+ + CLASS_PREFIX
+ + "Arg); \n}",
+ "-keep class "
+ + CLASS_PREFIX
+ + "Main2 { \npublic static void main(java.lang.String[]); \npublic static "
+ + CLASS_PREFIX
+ + "Arg getArg1(); \npublic static "
+ + CLASS_PREFIX
+ + "Arg getArg2(); \npublic static "
+ + CLASS_PREFIX
+ + "Call getCaller(); \n}"))
+ .enableNoVerticalClassMergingAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ CodeInspector inspector = compiled.inspector();
+ compiled.run(parameters.getRuntime(), Main2.class).assertSuccessWithOutputLines("Arg1");
+ testForD8()
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathFiles(compiled.writeToZip())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Arg1", "Arg2");
+ }
+
+ // Kept
+ @NoVerticalClassMerging
+ interface Arg {
+
+ @NeverInline
+ @NeverPropagateValue
+ String getString();
+ }
+
+ @NoVerticalClassMerging
+ @NoHorizontalClassMerging
+ static class Arg1 implements Arg {
+
+ @Override
+ @NeverInline
+ @NeverPropagateValue
+ public String getString() {
+ return "Arg1";
+ }
+ }
+
+ @NoVerticalClassMerging
+ @NoHorizontalClassMerging
+ static class Arg2 implements Arg {
+
+ @Override
+ @NeverInline
+ @NeverPropagateValue
+ public String getString() {
+ return "Arg2";
+ }
+ }
+
+ @NoVerticalClassMerging
+ interface Call {
+
+ // Kept.
+ @NeverInline
+ @NeverPropagateValue
+ void print(Arg arg);
+ }
+
+ @NoVerticalClassMerging
+ static class CallImpl implements Call {
+
+ @Override
+ @NeverInline
+ @NeverPropagateValue
+ public void print(Arg arg) {
+ System.out.println(arg.getString());
+ }
+ }
+
+ @NoVerticalClassMerging
+ static class Main2 {
+
+ // Kept.
+ public static void main(String[] args) {
+ // This would propagate Arg1 to print while it should not.
+ getCaller().print(new Arg1());
+ }
+
+ // Kept.
+ public static Arg getArg1() {
+ return new Arg1();
+ }
+
+ // Kept.
+ public static Arg getArg2() {
+ return new Arg2();
+ }
+
+ // Kept.
+ public static Call getCaller() {
+ return new CallSiteOptimizationPinnedMethodOverridePropagationTest.CallImpl();
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Arg arg1 = Main2.getArg1();
+ Arg arg2 = Main2.getArg2();
+ Call caller = Main2.getCaller();
+ caller.print(arg1);
+ caller.print(arg2);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index 2e3904a..339bbbc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -36,35 +36,42 @@
@RunWith(Parameterized.class)
public class UninstantiatedAnnotatedArgumentsTest extends TestBase {
+ private final boolean enableProguardCompatibilityMode;
private final boolean keepUninstantiatedArguments;
private final TestParameters parameters;
- @Parameters(name = "{1}, keep uninstantiated arguments: {0}")
+ @Parameters(name = "{2}, compat: {0}, keep uninstantiated arguments: {1}")
public static List<Object[]> params() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
public UninstantiatedAnnotatedArgumentsTest(
- boolean keepUninstantiatedArguments, TestParameters parameters) {
+ boolean enableProguardCompatibilityMode,
+ boolean keepUninstantiatedArguments,
+ TestParameters parameters) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
this.keepUninstantiatedArguments = keepUninstantiatedArguments;
this.parameters = parameters;
}
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addInnerClasses(UninstantiatedAnnotatedArgumentsTest.class)
.addConstantArgumentAnnotations()
.addKeepMainRule(TestClass.class)
.addKeepClassRules(Instantiated.class, Uninstantiated.class)
- .addKeepAttributes("RuntimeVisibleParameterAnnotations")
+ .addKeepRuntimeVisibleParameterAnnotations()
.enableNeverClassInliningAnnotations()
.enableConstantArgumentAnnotations(keepUninstantiatedArguments)
.enableInliningAnnotations()
.enableUnusedArgumentAnnotations()
// TODO(b/123060011): Mapping not working in presence of argument removal.
.minification(keepUninstantiatedArguments)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyOutput)
.run(parameters.getRuntime(), TestClass.class)
@@ -94,38 +101,35 @@
assertThat(methodSubject, isPresent());
// TODO(b/131735725): Should also remove arguments from the virtual methods.
- if (keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual")) {
- assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
- assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
+ boolean shouldHaveArgumentRemoval =
+ keepUninstantiatedArguments || methodSubject.getOriginalName().contains("Virtual");
+ if (shouldHaveArgumentRemoval) {
+ assertEquals(3, methodSubject.getMethod().getParameters().size());
- for (int i = 0; i < 3; ++i) {
- DexAnnotationSet annotationSet =
- methodSubject.getMethod().parameterAnnotationsList.get(i);
- assertEquals(1, annotationSet.annotations.length);
-
- DexAnnotation annotation = annotationSet.annotations[0];
- if (i == getPositionOfUnusedArgument(methodSubject)) {
- assertEquals(
- uninstantiatedClassSubject.getFinalName(),
- annotation.annotation.type.toSourceString());
- } else {
- assertEquals(
- instantiatedClassSubject.getFinalName(),
- annotation.annotation.type.toSourceString());
- }
- }
+ // In non-compat mode, R8 removes annotations from non-pinned items.
+ assertEquals(
+ enableProguardCompatibilityMode ? 3 : 0,
+ methodSubject.getMethod().getParameterAnnotations().size());
} else {
assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
- assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
+ assertEquals(
+ enableProguardCompatibilityMode ? 2 : 0,
+ methodSubject.getMethod().getParameterAnnotations().size());
+ }
- for (int i = 0; i < 2; ++i) {
- DexAnnotationSet annotationSet =
- methodSubject.getMethod().parameterAnnotationsList.get(i);
- assertEquals(1, annotationSet.annotations.length);
+ for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
+ DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
+ assertEquals(1, annotationSet.size());
- DexAnnotation annotation = annotationSet.annotations[0];
+ DexAnnotation annotation = annotationSet.getFirst();
+ if (shouldHaveArgumentRemoval && i == getPositionOfUnusedArgument(methodSubject)) {
assertEquals(
- instantiatedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+ uninstantiatedClassSubject.getFinalName(),
+ annotation.getAnnotationType().getTypeName());
+ } else {
+ assertEquals(
+ instantiatedClassSubject.getFinalName(),
+ annotation.getAnnotationType().getTypeName());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index 955c5d4..7cf6193 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -35,22 +35,30 @@
@RunWith(Parameterized.class)
public class UnusedAnnotatedArgumentsTest extends TestBase {
+ private final boolean enableProguardCompatibilityMode;
private final boolean keepUnusedArguments;
private final TestParameters parameters;
- @Parameters(name = "{1}, keep unused arguments: {0}")
+ @Parameters(name = "{2}, compat: {0}, keep unused arguments: {1}")
public static List<Object[]> params() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public UnusedAnnotatedArgumentsTest(boolean keepUnusedArguments, TestParameters parameters) {
+ public UnusedAnnotatedArgumentsTest(
+ boolean enableProguardCompatibilityMode,
+ boolean keepUnusedArguments,
+ TestParameters parameters) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
this.keepUnusedArguments = keepUnusedArguments;
this.parameters = parameters;
}
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addInnerClasses(UnusedAnnotatedArgumentsTest.class)
.addUnusedArgumentAnnotations()
.addKeepMainRule(TestClass.class)
@@ -61,7 +69,7 @@
.enableUnusedArgumentAnnotations(keepUnusedArguments)
// TODO(b/123060011): Mapping not working in presence of unused argument removal.
.minification(keepUnusedArguments)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyOutput)
.run(parameters.getRuntime(), TestClass.class)
@@ -90,36 +98,24 @@
for (MethodSubject methodSubject : methodSubjects) {
assertThat(methodSubject, isPresent());
- if (keepUnusedArguments) {
- assertEquals(3, methodSubject.getMethod().getReference().proto.parameters.size());
- assertEquals(3, methodSubject.getMethod().parameterAnnotationsList.size());
+ assertEquals(keepUnusedArguments ? 3 : 2, methodSubject.getMethod().getParameters().size());
- for (int i = 0; i < 3; ++i) {
- DexAnnotationSet annotationSet =
- methodSubject.getMethod().parameterAnnotationsList.get(i);
- assertEquals(1, annotationSet.annotations.length);
+ // R8 non-compat removes annotations from non-pinned items.
+ assertEquals(
+ enableProguardCompatibilityMode ? methodSubject.getMethod().getParameters().size() : 0,
+ methodSubject.getMethod().getParameterAnnotations().size());
- DexAnnotation annotation = annotationSet.annotations[0];
- if (i == getPositionOfUnusedArgument(methodSubject)) {
- assertEquals(
- unusedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
- } else {
- assertEquals(
- usedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
- }
- }
- } else {
- assertEquals(2, methodSubject.getMethod().getReference().proto.parameters.size());
- assertEquals(2, methodSubject.getMethod().parameterAnnotationsList.size());
+ for (int i = 0; i < methodSubject.getMethod().getParameterAnnotations().size(); ++i) {
+ DexAnnotationSet annotationSet = methodSubject.getMethod().getParameterAnnotation(i);
+ assertEquals(1, annotationSet.size());
- for (int i = 0; i < 2; ++i) {
- DexAnnotationSet annotationSet =
- methodSubject.getMethod().parameterAnnotationsList.get(i);
- assertEquals(1, annotationSet.annotations.length);
-
- DexAnnotation annotation = annotationSet.annotations[0];
+ DexAnnotation annotation = annotationSet.annotations[0];
+ if (keepUnusedArguments && i == getPositionOfUnusedArgument(methodSubject)) {
assertEquals(
- usedClassSubject.getFinalName(), annotation.annotation.type.toSourceString());
+ unusedClassSubject.getFinalName(), annotation.getAnnotationType().toSourceString());
+ } else {
+ assertEquals(
+ usedClassSubject.getFinalName(), annotation.getAnnotationType().toSourceString());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
index 49b90e7..c008972 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsWithMissingAnnotationsTest.java
@@ -11,10 +11,11 @@
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.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,14 +29,18 @@
public class UnusedAnnotatedArgumentsWithMissingAnnotationsTest extends TestBase
implements Opcodes {
+ private final boolean enableProguardCompatibilityMode;
private final TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameterized.Parameters(name = "{1}, compat: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(TestParameters parameters) {
+ public UnusedAnnotatedArgumentsWithMissingAnnotationsTest(
+ boolean enableProguardCompatibilityMode, TestParameters parameters) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
this.parameters = parameters;
}
@@ -43,26 +48,28 @@
assertThat(clazz, isPresent());
MethodSubject init = clazz.init("Test", "java.lang.String");
assertThat(init, isPresent());
- assertEquals(2, init.getMethod().parameterAnnotationsList.size());
- assertTrue(init.getMethod().parameterAnnotationsList.get(0).isEmpty());
- assertEquals(1, init.getMethod().parameterAnnotationsList.get(1).annotations.length);
- assertEquals(
- "L" + expectedAnnotationClass + ";",
- init.getMethod()
- .parameterAnnotationsList
- .get(1)
- .annotations[0]
- .annotation
- .type
- .descriptor
- .toString());
+ if (enableProguardCompatibilityMode) {
+ assertEquals(2, init.getMethod().getParameterAnnotations().size());
+ assertTrue(init.getMethod().getParameterAnnotation(0).isEmpty());
+ assertEquals(1, init.getMethod().getParameterAnnotation(1).size());
+ assertEquals(
+ "L" + expectedAnnotationClass + ";",
+ init.getMethod()
+ .getParameterAnnotation(1)
+ .annotations[0]
+ .annotation
+ .type
+ .toDescriptorString());
+ } else {
+ assertTrue(init.getMethod().getParameterAnnotations().isEmpty());
+ }
}
@Test
public void test() throws Exception {
String expectedOutput =
StringUtils.lines("In Inner1() used", "In Inner2() used", "In Inner3() used", "In main()");
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addProgramClassFileData(
dumpTest(),
dumpInner1(),
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationIfTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationIfTest.java
new file mode 100644
index 0000000..5e0fa6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationIfTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/190623364.
+@RunWith(Parameterized.class)
+public class MainDexRemovedAnnotationIfTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+ .build();
+ }
+
+ public MainDexRemovedAnnotationIfTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMainDexTracing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MainDex.class, Inside.class, Main.class)
+ .addKeepClassAndMembersRules(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .addMainDexRules(
+ "-if @" + MainDex.class.getTypeName() + " class *", "-keep class <1> { *; }")
+ .collectMainDexClasses()
+ .compile()
+ .inspectMainDexClasses(
+ mainDexClasses -> {
+ // TODO(b/190623364): Should not be empty.
+ assertTrue(mainDexClasses.isEmpty());
+ })
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(MainDex.class), not(isPresent()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE})
+ public @interface MainDex {}
+
+ @MainDex
+ public static class Inside {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Inside.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationTest.java
new file mode 100644
index 0000000..67b113c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRemovedAnnotationTest.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2021, 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/190623364.
+@RunWith(Parameterized.class)
+public class MainDexRemovedAnnotationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+ .build();
+ }
+
+ public MainDexRemovedAnnotationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testMainDexTracing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(MainDex.class, Inside.class, Main.class)
+ .addKeepClassAndMembersRules(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .addMainDexRules("-keep @" + MainDex.class.getTypeName() + " class * { *; }")
+ .collectMainDexClasses()
+ .compile()
+ .inspectMainDexClasses(
+ mainDexClasses -> {
+ // TODO(b/190623364): Should not be empty.
+ assertTrue(mainDexClasses.isEmpty());
+ })
+ .inspect(
+ codeInspector -> {
+ assertThat(codeInspector.clazz(MainDex.class), not(isPresent()));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE})
+ public @interface MainDex {}
+
+ @MainDex
+ public static class Inside {
+
+ @NeverInline
+ public static void foo() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Inside.foo();
+ }
+ }
+}
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 cc558a1..5ed7de6 100644
--- a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveTest.java
@@ -171,8 +171,7 @@
.inspector();
}
- private CodeInspector compileR8(Class... classes)
- throws CompilationFailedException, IOException, ExecutionException {
+ private CodeInspector compileR8(Class... classes) throws CompilationFailedException, IOException {
List<String> keepRules =
Arrays.stream(classes)
.map(c -> "-keep class " + c.getCanonicalName() + " { <methods>; }")
@@ -186,11 +185,16 @@
// Keep the input class and its methods.
.addKeepRules(keepRules)
// Keep the annotation class.
- .addKeepRules("-keep class dalvik.annotation.optimization.ReachabilitySensitive")
+ .addKeepRules(
+ "-keep class dalvik.annotation.optimization.ReachabilitySensitive",
+ "-keep,allowshrinking,allowobfuscation class * {",
+ " @dalvik.annotation.optimization.ReachabilitySensitive <fields>;",
+ " @dalvik.annotation.optimization.ReachabilitySensitive <methods>;",
+ "}")
// Keep the annotation so R8 can find it and honor it. It also needs to be available
// at runtime so that the Art runtime can honor it as well, so if it is not kept we
// do not have to honor it as the runtime will not know to do so in any case.
- .addKeepRules("-keepattributes RuntimeVisibleAnnotations")
+ .addKeepRuntimeVisibleAnnotations()
.compile()
.inspector();
}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
index 61526d4..fe19c51 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageAnnotationTest.java
@@ -4,29 +4,61 @@
package com.android.tools.r8.repackage;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+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 RepackageAnnotationTest extends RepackageTestBase {
- private final String EXPECTED = "Hello World";
+ private static final String EXPECTED = "Hello World";
+ private static final String EXPECTED_WITH_ANNOTATION_REMOVAL = "null";
+
+ private final boolean enableProguardCompatibilityMode;
+ private final boolean keepAllowShrinking;
+
+ @Parameters(name = "{3}, compat: {0}, keep: {1}, kind: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
public RepackageAnnotationTest(
- String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+ boolean enableProguardCompatibilityMode,
+ boolean keepAllowShrinking,
+ String flattenPackageHierarchyOrRepackageClasses,
+ TestParameters parameters) {
super(flattenPackageHierarchyOrRepackageClasses, parameters);
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+ this.keepAllowShrinking = keepAllowShrinking;
}
@Test
public void testRuntime() throws Exception {
+ assumeFalse(enableProguardCompatibilityMode);
+ assumeFalse(keepAllowShrinking);
+ assumeFalse(isFlattenPackageHierarchy());
testForRuntime(parameters)
.addProgramClasses(Main.class, Annotation.class, A.class)
.run(parameters.getRuntime(), Main.class)
@@ -35,8 +67,17 @@
@Test
public void testR8() throws Exception {
- testForR8(parameters.getBackend())
+ assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addProgramClasses(Main.class, A.class, Annotation.class)
+ .applyIf(
+ keepAllowShrinking,
+ builder -> {
+ // Add a keep rule to ensure annotation is retained with R8 non-compat.
+ assertFalse(enableProguardCompatibilityMode);
+ builder.addKeepRules(
+ "-keep,allowshrinking,allowobfuscation class " + A.class.getTypeName());
+ })
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.enableInliningAnnotations()
@@ -47,8 +88,13 @@
" *;",
"}")
.apply(this::configureRepackaging)
+ .compile()
+ .inspect(inspector -> assertThat(Annotation.class, isRepackaged(inspector)))
.run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines(EXPECTED);
+ .assertSuccessWithOutputLines(
+ enableProguardCompatibilityMode || keepAllowShrinking
+ ? EXPECTED
+ : EXPECTED_WITH_ANNOTATION_REMOVAL);
}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
index 4672079..34cd1af 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateClassAnnotationTest.java
@@ -30,7 +30,7 @@
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
index 46a83ff..af48e82 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldAnnotationTest.java
@@ -31,7 +31,7 @@
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
index 068cc89..20f8704 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodAnnotationTest.java
@@ -30,7 +30,7 @@
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
index c7cd698..d9833ae 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateMethodParameterAnnotationTest.java
@@ -30,7 +30,7 @@
@Test
public void test() throws Exception {
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.addKeepClassRules(NonPublicKeptAnnotation.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 33dfbaf..926b2c0 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -279,7 +279,7 @@
+ " *** *(...); } "
+ "-keep class <1> { @"
+ PRESENT_ANNOTATION
- + " *** <2>(...); }")
+ + " <2> <3>(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
.apply(this::configureHorizontalClassMerging)
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
new file mode 100644
index 0000000..6e25e4ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AlwaysRetainRetentionAnnotationTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.enums.EnumInAnnotationTest.MyAnnotation;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+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 AlwaysRetainRetentionAnnotationTest extends TestBase {
+
+ private final boolean enableProguardCompatibilityMode;
+ private final boolean keepAllowShrinking;
+ private final TestParameters parameters;
+
+ @Parameters(name = "{2}, compat: {0}, keep: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public AlwaysRetainRetentionAnnotationTest(
+ boolean enableProguardCompatibilityMode,
+ boolean keepAllowShrinking,
+ TestParameters parameters) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+ this.keepAllowShrinking = keepAllowShrinking;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .addKeepRuntimeVisibleAnnotations()
+ .applyIf(
+ keepAllowShrinking,
+ builder -> {
+ assertFalse(enableProguardCompatibilityMode);
+ builder.addKeepRules(
+ "-keep,allowshrinking,allowobfuscation class "
+ + MyAnnotation.class.getTypeName());
+ })
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject annotationClassSubject = inspector.clazz(MyAnnotation.class);
+ assertThat(annotationClassSubject, isPresent());
+
+ AnnotationSubject retentionAnnotationSubject =
+ annotationClassSubject.annotation(Retention.class.getTypeName());
+ assertThat(retentionAnnotationSubject, isPresent());
+
+ AnnotationSubject targetAnnotationSubject =
+ annotationClassSubject.annotation(Target.class.getTypeName());
+ assertThat(targetAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
+
+ AnnotationSubject myAnnotationAnnotationSubject =
+ annotationClassSubject.annotation(MyAnnotation.class.getTypeName());
+ assertThat(
+ myAnnotationAnnotationSubject, onlyIf(shouldOnlyRetainRetention(), isAbsent()));
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines(shouldOnlyRetainRetention() ? "1" : "3");
+ }
+
+ private boolean shouldOnlyRetainRetention() {
+ return !enableProguardCompatibilityMode && !keepAllowShrinking;
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(MyAnnotation.class.getAnnotations().length);
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE})
+ @MyAnnotation
+ @interface MyAnnotation {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
index 9c98c6e..e98541c 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
@@ -59,6 +59,10 @@
.addProgramClasses(Factory.class, MainWithMethodAnnotation.class, C.class)
.addKeepMainRule(MainWithMethodAnnotation.class)
.addKeepClassAndMembersRules(Factory.class)
+ .addKeepRules(
+ "-keepclassmembers,allowobfuscation,allowshrinking class "
+ + MainWithMethodAnnotation.class.getTypeName()
+ + "{void test();}")
.addKeepAttributes("*Annotation*")
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -123,7 +127,7 @@
test();
}
- @Factory(ref = C.class) // <-- We are not explicitly saying that test() should be kept.
+ @Factory(ref = C.class) // <-- We are explicitly saying that test() should be kept.
@NeverInline
public static void test() {
System.out.println("Hello World!");
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
index 9246164..d737c41 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationsOnTargetedMethodTest.java
@@ -4,13 +4,16 @@
package com.android.tools.r8.shaking.annotations;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
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.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
@@ -18,6 +21,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -32,29 +36,56 @@
"In OtherInterfaceImpl.targetedMethod()",
MyAnnotation.class.getName());
+ private static final String expectedOutputWithAnnotationRemoval =
+ StringUtils.lines(
+ "In InterfaceImpl.targetedMethod()", "In OtherInterfaceImpl.targetedMethod()");
+
+ private final boolean enableProguardCompatibilityMode;
+ private final boolean keepAllowShrinking;
private final TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameters(name = "{2}, compat: {0}, keep: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public AnnotationsOnTargetedMethodTest(TestParameters parameters) {
+ public AnnotationsOnTargetedMethodTest(
+ boolean enableProguardCompatibilityMode,
+ boolean keepAllowShrinking,
+ TestParameters parameters) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+ this.keepAllowShrinking = keepAllowShrinking;
this.parameters = parameters;
}
@Test
public void test() throws Exception {
+ // No need to run R8 compat mode with extra -keep,allowshrinking rule.
+ assumeTrue(!enableProguardCompatibilityMode || !keepAllowShrinking);
if (parameters.isCfRuntime()) {
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expectedOutput);
}
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addInnerClasses(AnnotationsOnTargetedMethodTest.class)
.addKeepMainRule(TestClass.class)
.addKeepRuntimeVisibleAnnotations()
+ .applyIf(
+ keepAllowShrinking,
+ builder -> {
+ // Add extra rule to retain the annotation on Interface.targetedMethod() in non-compat
+ // mode.
+ assertFalse(enableProguardCompatibilityMode); // See assumeTrue() above.
+ builder.addKeepRules(
+ "-keepclassmembers,allowshrinking class " + Interface.class.getTypeName() + " {",
+ " void targetedMethod();",
+ "}");
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
@@ -62,7 +93,10 @@
.noMinification()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(expectedOutput);
+ .assertSuccessWithOutput(
+ !enableProguardCompatibilityMode && !keepAllowShrinking
+ ? expectedOutputWithAnnotationRemoval
+ : expectedOutput);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
index 13fbb3e..da0b781 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+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.MethodSubject;
@@ -43,12 +45,21 @@
}
@Test
- public void test() throws Exception {
+ public void testCompat() throws Exception {
+ runTest(true);
+ }
+
+ @Test
+ public void testNonCompat() throws Exception {
+ runTest(false);
+ }
+
+ private void runTest(boolean enableCompatMode) throws Exception {
R8TestRunResult result =
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend(), enableCompatMode)
.addInnerClasses(ProgramAnnotationRemovalTest.class)
.addKeepMainRule(TestClass.class)
- .addKeepAttributes("RuntimeVisibleAnnotations")
+ .addKeepRuntimeVisibleAnnotations()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -68,14 +79,19 @@
MethodSubject methodWithLiveProgramAnnotationSubject =
testClassSubject.uniqueMethodWithName("methodWithLiveProgramAnnotation");
assertThat(methodWithLiveProgramAnnotationSubject, isPresent());
- assertEquals(1, methodWithLiveProgramAnnotationSubject.getMethod().annotations().size());
+ assertEquals(
+ BooleanUtils.intValue(enableCompatMode),
+ methodWithLiveProgramAnnotationSubject.getMethod().annotations().size());
MethodSubject methodWithDeadProgramAnnotationSubject =
testClassSubject.uniqueMethodWithName("methodWithDeadProgramAnnotation");
assertThat(methodWithDeadProgramAnnotationSubject, isPresent());
assertEquals(0, methodWithDeadProgramAnnotationSubject.getMethod().annotations().size());
- result.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()");
+ result.applyIf(
+ enableCompatMode,
+ r -> r.assertSuccessWithOutputLines("@" + liveAnnotationClassSubject.getFinalName() + "()"),
+ TestRunResult::assertSuccessWithEmptyOutput);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
index 16c7d4f..187d79c 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/PrunedOrMergedAnnotationTest.java
@@ -8,21 +8,27 @@
import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,27 +38,50 @@
@RunWith(Parameterized.class)
public class PrunedOrMergedAnnotationTest extends TestBase {
+ private final boolean enableProguardCompatibilityMode;
+ private final boolean keepForAnnotations;
private final TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameters(name = "{2}, compat: {0}, keep: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public PrunedOrMergedAnnotationTest(TestParameters parameters) {
+ public PrunedOrMergedAnnotationTest(
+ boolean enableProguardCompatibilityMode,
+ boolean keepForAnnotations,
+ TestParameters parameters) {
+ this.enableProguardCompatibilityMode = enableProguardCompatibilityMode;
+ this.keepForAnnotations = keepForAnnotations;
this.parameters = parameters;
}
@Test
public void testRewritingInFactory()
throws IOException, CompilationFailedException, ExecutionException {
- testForR8(parameters.getBackend())
+ // No need to add extra keep rules for retaining annotations in compat mode.
+ assumeTrue(!enableProguardCompatibilityMode || !keepForAnnotations);
+ testForR8Compat(parameters.getBackend(), enableProguardCompatibilityMode)
.addInnerClasses(PrunedOrMergedAnnotationTest.class)
.addKeepMainRule(Main.class)
- .addKeepAttributes("*Annotation*")
.addKeepClassAndMembersRules(Factory.class)
+ .addKeepRuntimeInvisibleAnnotations()
+ .addKeepRuntimeInvisibleParameterAnnotations()
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .applyIf(
+ keepForAnnotations,
+ builder -> {
+ assertFalse(enableProguardCompatibilityMode);
+ builder.addKeepRules(
+ "-keep,allowshrinking,allowobfuscation class " + C.class.getTypeName());
+ })
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello", "World!")
@@ -62,15 +91,21 @@
DexType mergedType = inspector.clazz(B.class).getDexProgramClass().type;
ClassSubject classC = inspector.clazz(C.class);
assertThat(classC, isPresent());
- DexEncodedAnnotation annotation =
- classC.annotation(Factory.class.getTypeName()).getAnnotation();
- assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
- assertTrue(
- Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
- .allMatch(value -> valueIsDexType(mergedType, value)));
- // Check that method parameter annotations are rewritten as well.
- DexEncodedMethod method = inspector.clazz(Main.class).mainMethod().getMethod();
- DexAnnotationSet annotationSet = method.parameterAnnotationsList.get(0);
+
+ MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+ if (enableProguardCompatibilityMode || keepForAnnotations) {
+ DexEncodedAnnotation annotation =
+ classC.annotation(Factory.class.getTypeName()).getAnnotation();
+ assertTrue(valueIsDexType(mergedType, annotation.elements[0].value));
+ assertTrue(
+ Arrays.stream(annotation.elements[1].value.asDexValueArray().getValues())
+ .allMatch(value -> valueIsDexType(mergedType, value)));
+ } else {
+ assertTrue(classC.getDexProgramClass().annotations().isEmpty());
+ }
+
+ // Check that method parameter annotations are rewritten.
+ DexAnnotationSet annotationSet = mainMethod.getMethod().getParameterAnnotation(0);
DexEncodedAnnotation parameterAnnotation = annotationSet.annotations[0].annotation;
assertTrue(valueIsDexType(mergedType, parameterAnnotation.elements[0].value));
});
@@ -82,6 +117,7 @@
return true;
}
+ @Retention(RetentionPolicy.CLASS)
public @interface Factory {
Class<?> extending() default Object.class;
@@ -92,6 +128,7 @@
public static class A {}
@NeverClassInline
+ @NoHorizontalClassMerging
public static class B extends A {
@NeverInline
public void world() {
@@ -102,6 +139,7 @@
@Factory(
extending = A.class,
other = {A.class, B.class})
+ @NoHorizontalClassMerging
public static class C {
@NeverInline
public static void hello() {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index f102535..662ad5e 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -95,7 +95,7 @@
@Test
public void b120951621_keepAll() throws Exception {
CodeInspector inspector =
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addProgramFiles(
compiledJars.getForConfiguration(kotlinc, targetVersion),
getKotlinAnnotationJar(kotlinc))
@@ -133,7 +133,7 @@
@Test
public void b120951621_partiallyKeep() throws Exception {
CodeInspector inspector =
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addProgramFiles(
compiledJars.getForConfiguration(kotlinc, targetVersion),
getKotlinAnnotationJar(kotlinc))
@@ -175,7 +175,7 @@
@Test
public void b120951621_keepAnnotation() throws Exception {
CodeInspector inspector =
- testForR8(parameters.getBackend())
+ testForR8Compat(parameters.getBackend())
.addProgramFiles(
compiledJars.getForConfiguration(kotlinc, targetVersion),
getKotlinAnnotationJar(kotlinc))
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index ddc9a2b..184cd68 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -71,6 +71,16 @@
"com.squareup.wire.WireField", "com.squareup.demo.myapplication.Test")
.addKeepMainRule(TestClass.class)
.addKeepAttributes("*Annotation*")
+ .applyIf(
+ parameters.isCfRuntime(),
+ builder ->
+ // When parsing the enum default value, the JVM tries to find the enum with the
+ // given name, but after shrinking the enum field names and the enum instance names
+ // no longer match.
+ builder.addKeepRules(
+ "-keepclassmembers,allowshrinking class com.squareup.wire.WireField$Label {",
+ " static com.squareup.wire.WireField$Label OPTIONAL;",
+ "}"))
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::checkEnumUses)
diff --git a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
index d42d3ad..50f1954 100644
--- a/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/enums/EnumInAnnotationTest.java
@@ -43,8 +43,14 @@
testForR8(parameters.getBackend())
.addInnerClasses(EnumInAnnotationTest.class)
.addKeepMainRule(Main.class)
+ .applyIf(
+ parameters.isCfRuntime(),
+ builder ->
+ builder.addKeepRules(
+ "-keepclassmembernames class " + Enum.class.getTypeName() + " { <fields>; }"))
.setMinApi(parameters.getApiLevel())
.addKeepRuntimeVisibleAnnotations()
+ .compile()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("TEST_ONE");
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 9933b84..e00615a 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -6,7 +6,9 @@
import static com.android.tools.r8.references.Reference.classFromClass;
import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -122,7 +124,6 @@
mainClass, MentionedClass.class, mentionedClassWithAnnotations, annotationClass)
.addKeepMainRule(mainClass)
.allowAccessModification()
- .noMinification()
.addKeepClassAndMembersRules(annotationClass)
.map(b -> keepAnnotations ? b.addKeepAttributes("*Annotation*") : b)
.setMinApi(parameters.getApiLevel())
@@ -134,10 +135,15 @@
assertThat(clazz, isPresent());
// The test contains only a member class so the enclosing-method attribute will be null.
- assertEquals(
- forceProguardCompatibility, !clazz.getDexProgramClass().getInnerClasses().isEmpty());
- assertEquals(forceProguardCompatibility || keepAnnotations,
- clazz.annotation(annotationClass.getCanonicalName()).isPresent());
+ assertTrue(clazz.getDexProgramClass().getInnerClasses().isEmpty());
+
+ if (keepAnnotations) {
+ assertThat(
+ clazz.annotation(annotationClass.getCanonicalName()),
+ onlyIf(forceProguardCompatibility, isPresent()));
+ } else {
+ assertThat(clazz.annotation(annotationClass.getCanonicalName()), isAbsent());
+ }
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index 880ed52..edbcd98 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -54,8 +54,11 @@
CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- verifyClassesPresent(codeInspector,
- UsedAnnotation.class, UsedAnnotationDependent.class);
+ if (shrinker.isFullModeR8()) {
+ verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+ } else {
+ verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+ }
}
@Test
@@ -80,8 +83,11 @@
CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- verifyClassesPresent(codeInspector,
- UsedAnnotation.class, UsedAnnotationDependent.class);
+ if (shrinker.isFullModeR8()) {
+ verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+ } else {
+ verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+ }
}
@Test
@@ -104,8 +110,11 @@
CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
verifyClassesAbsent(codeInspector,
UnusedAnnotation.class, UnusedAnnotationDependent.class);
- verifyClassesPresent(codeInspector,
- UsedAnnotation.class, UsedAnnotationDependent.class);
+ if (shrinker.isFullModeR8()) {
+ verifyClassesAbsent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+ } else {
+ verifyClassesPresent(codeInspector, UsedAnnotation.class, UsedAnnotationDependent.class);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
index d578ae2..b97fd41 100644
--- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -6,12 +6,29 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import java.util.Map;
import java.util.function.Function;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
-public class ArrayUtilsTest {
+@RunWith(Parameterized.class)
+public class ArrayUtilsTest extends TestBase {
+
+ private static final Integer[] EMPTY_INTEGER_ARRAY = new Integer[0];
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ArrayUtilsTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
private Integer[] createInputData(int size) {
Integer[] input = new Integer[size];
@@ -124,7 +141,7 @@
public void testMap_identity() {
int size = 3;
Integer[] input = createInputData(size);
- Integer[] output = ArrayUtils.map(Integer[].class, input, Function.identity());
+ Integer[] output = ArrayUtils.map(input, Function.identity(), EMPTY_INTEGER_ARRAY);
assertEquals(input, output);
}
@@ -132,7 +149,7 @@
public void testMap_dropOdd() {
int size = 3;
Integer[] input = createInputData(size);
- Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? null : x);
+ Integer[] output = ArrayUtils.map(input, x -> x % 2 != 0 ? null : x, EMPTY_INTEGER_ARRAY);
assertNotEquals(input, output);
assertEquals(2, output.length);
assertEquals(0, (int) output[0]);
@@ -143,7 +160,7 @@
public void testMap_dropAll() {
int size = 3;
Integer[] input = createInputData(size);
- Integer[] output = ArrayUtils.map(Integer[].class, input, x -> null);
+ Integer[] output = ArrayUtils.map(input, x -> null, EMPTY_INTEGER_ARRAY);
assertNotEquals(input, output);
assertEquals(0, output.length);
}
@@ -152,7 +169,7 @@
public void testMap_double() {
int size = 3;
Integer[] input = createInputData(size);
- Integer[] output = ArrayUtils.map(Integer[].class, input, x -> 2 * x);
+ Integer[] output = ArrayUtils.map(input, x -> 2 * x, EMPTY_INTEGER_ARRAY);
assertNotEquals(input, output);
assertEquals(size, output.length);
for (int i = 0; i < size; i++) {
@@ -164,7 +181,7 @@
public void testMap_double_onlyOdd() {
int size = 3;
Integer[] input = createInputData(size);
- Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? 2 * x : x);
+ Integer[] output = ArrayUtils.map(input, x -> x % 2 != 0 ? 2 * x : x, EMPTY_INTEGER_ARRAY);
assertNotEquals(input, output);
assertEquals(size, output.length);
for (int i = 0; i < size; i++) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
index ceafd09..6d7067a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.FieldReference;
public class AbsentFieldSubject extends FieldSubject {
@@ -68,6 +69,16 @@
}
@Override
+ public FieldReference getOriginalReference() {
+ return null;
+ }
+
+ @Override
+ public FieldReference getFinalReference() {
+ return null;
+ }
+
+ @Override
public AccessFlags<?> getAccessFlags() {
throw new Unreachable("Absent field has no access flags");
}
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 0a4f65a..452669a 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
@@ -193,6 +193,7 @@
public abstract AnnotationSubject annotation(String name);
+ @Override
public abstract String getOriginalName();
public abstract String getOriginalDescriptor();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index aae1912..9b9d373 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.references.FieldReference;
public abstract class FieldSubject extends MemberSubject {
@@ -43,4 +44,8 @@
}
public abstract String getJvmFieldSignatureAsString();
+
+ public abstract FieldReference getOriginalReference();
+
+ public abstract FieldReference getFinalReference();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index 26466c1..023c088 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -13,6 +13,8 @@
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
public class FoundFieldSubject extends FieldSubject {
@@ -130,6 +132,23 @@
}
@Override
+ public FieldReference getOriginalReference() {
+ DexField originalDexField = getOriginalDexField(codeInspector.getFactory());
+ return Reference.field(
+ Reference.classFromDescriptor(originalDexField.holder.toDescriptorString()),
+ getOriginalName(),
+ Reference.typeFromDescriptor(originalDexField.type.toDescriptorString()));
+ }
+
+ @Override
+ public FieldReference getFinalReference() {
+ return Reference.field(
+ Reference.classFromDescriptor(getField().getHolderType().toDescriptorString()),
+ getOriginalName(),
+ Reference.typeFromDescriptor(getField().getType().toDescriptorString()));
+ }
+
+ @Override
public AccessFlags<?> getAccessFlags() {
return getField().getAccessFlags();
}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index b08651f..e51c162 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
@@ -624,6 +625,14 @@
private final Map<MethodReference, MethodGraphNode> methods;
private final Map<FieldReference, FieldGraphNode> fields;
+ // Maps (annotated item, annotation type) to annotation node.
+ private final Map<ClassReference, Map<ClassReference, AnnotationGraphNode>> classAnnotations =
+ new HashMap<>();
+ private final Map<FieldReference, Map<ClassReference, AnnotationGraphNode>> fieldAnnotations =
+ new HashMap<>();
+ private final Map<MethodReference, Map<ClassReference, AnnotationGraphNode>> methodAnnotations =
+ new HashMap<>();
+
public GraphInspector(CollectingGraphConsumer consumer, CodeInspector inspector) {
this.consumer = consumer;
this.inspector = inspector;
@@ -646,8 +655,30 @@
} else if (target instanceof KeepRuleGraphNode) {
KeepRuleGraphNode node = (KeepRuleGraphNode) target;
rules.add(node);
+ } else if (target instanceof AnnotationGraphNode) {
+ AnnotationGraphNode node = (AnnotationGraphNode) target;
+ GraphNode annotatedNode = node.getAnnotatedNode();
+ Map<ClassReference, AnnotationGraphNode> annotationsOnAnnotatedNode;
+ if (annotatedNode instanceof ClassGraphNode) {
+ annotationsOnAnnotatedNode =
+ classAnnotations.computeIfAbsent(
+ ((ClassGraphNode) annotatedNode).getReference(), key -> new HashMap<>());
+ } else if (annotatedNode instanceof FieldGraphNode) {
+ annotationsOnAnnotatedNode =
+ fieldAnnotations.computeIfAbsent(
+ ((FieldGraphNode) annotatedNode).getReference(), key -> new HashMap<>());
+ } else if (annotatedNode instanceof MethodGraphNode) {
+ annotationsOnAnnotatedNode =
+ methodAnnotations.computeIfAbsent(
+ ((MethodGraphNode) annotatedNode).getReference(), key -> new HashMap<>());
+ } else {
+ throw new Unreachable(
+ "Incomplete support for annotations on non-class, non-field, non-method items: "
+ + annotatedNode.getClass().getTypeName());
+ }
+ annotationsOnAnnotatedNode.put(node.getAnnotationClassNode().getReference(), node);
} else {
- throw new Unimplemented("Incomplet support for graph node type: " + target.getClass());
+ throw new Unimplemented("Incomplete support for graph node type: " + target.getClass());
}
Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
for (GraphNode source : sources.keySet()) {
diff --git a/third_party/android_jar/api-database.tar.gz.sha1 b/third_party/android_jar/api-database.tar.gz.sha1
new file mode 100644
index 0000000..84abf49
--- /dev/null
+++ b/third_party/android_jar/api-database.tar.gz.sha1
@@ -0,0 +1 @@
+e4da4b29079ac393e0012e7676dcca0799841e29
\ No newline at end of file
diff --git a/third_party/android_jar/api-versions.tar.gz.sha1 b/third_party/android_jar/api-versions.tar.gz.sha1
new file mode 100644
index 0000000..849abf3
--- /dev/null
+++ b/third_party/android_jar/api-versions.tar.gz.sha1
@@ -0,0 +1 @@
+021b38c29b9be789f4312f95543a3f08baf66a19
\ No newline at end of file
diff --git a/third_party/android_jar/lib-v30.tar.gz.sha1 b/third_party/android_jar/lib-v30.tar.gz.sha1
index 700ad7c..31ebc31 100644
--- a/third_party/android_jar/lib-v30.tar.gz.sha1
+++ b/third_party/android_jar/lib-v30.tar.gz.sha1
@@ -1 +1 @@
-629e0bfb886d247246268130d946678fa1a4ca9e
\ No newline at end of file
+0173220e35349154afdde596c5bfaf21cb7df486
\ No newline at end of file
diff --git a/third_party/android_jar/lib-v31.tar.gz.sha1 b/third_party/android_jar/lib-v31.tar.gz.sha1
index 20eb896..0af2989 100644
--- a/third_party/android_jar/lib-v31.tar.gz.sha1
+++ b/third_party/android_jar/lib-v31.tar.gz.sha1
@@ -1 +1 @@
-758bc25bf1aa97fab86205b0dd5db396f9b129cd
\ No newline at end of file
+3f0543d825941eee32abca750ce30e83ed451f30
\ No newline at end of file
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 7914bf9..bf98dd3 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -394,7 +394,13 @@
change_result = g4_change(options.version)
change_result += 'Run \'(g4d ' + args.p4_client \
+ ' && tap_presubmit -p all --train -c ' \
- + get_cl_id(change_result) + ')\' for running TAP presubmit.'
+ + get_cl_id(change_result) + ')\' for running TAP global' \
+ + ' presubmit using the train.\n' \
+ + 'Run \'(g4d ' + args.p4_client \
+ + ' && tap_presubmit -p all --notrain --detach --email' \
+ + ' --skip_flaky_targets --skip_already_failing -c ' \
+ + get_cl_id(change_result) + ')\' for running an isolated' \
+ + ' TAP global presubmit.'
return change_result
return release_google3