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