Merge commit '712a8cafb44ae7264f7f4a1614da452239e735f0' into dev-release
Change-Id: I1596cc8938eb7a85540dde68e8c1421c1b6e05bc
diff --git a/doc/keepanno-guide.md b/doc/keepanno-guide.md
index c41aa8b..c924bdcf 100644
--- a/doc/keepanno-guide.md
+++ b/doc/keepanno-guide.md
@@ -23,6 +23,7 @@
- [Annotating code using reflection](#using-reflection)
- [Invoking methods](#using-reflection-methods)
- [Accessing fields](#using-reflection-fields)
+ - [Accessing annotations](#using-reflection-annotations)
- [Annotating code used by reflection (or via JNI)](#used-by-reflection)
- [Annotating APIs](#apis)
- [Migrating rules to annotations](#migrating-rules)
@@ -151,6 +152,79 @@
```
+### Accessing annotations<a name="using-reflection-annotations"></a>
+
+If your program is reflectively inspecting annotations on classes, methods or fields, you
+will need to declare additional "annotation constraints" about what assumptions are made
+about the annotations.
+
+In the following example, we have defined an annotation that will record the printing name we
+would like to use for fields instead of printing the concrete field name. That may be useful
+so that the field can be renamed to follow coding conventions for example.
+
+We are only interested in matching objects that contain fields annotated by `MyNameAnnotation`,
+that is specified using [@KeepTarget.fieldAnnotatedByClassConstant](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#fieldAnnotatedByClassConstant()).
+
+At runtime we need to be able to find the annotation too, so we add a constraint on the
+annotation using [@KeepTarget.constrainAnnotations](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepTarget.html#constrainAnnotations()).
+
+Finally, for the sake of example, we don't actually care about the name of the fields
+themselves, so we explicitly declare the smaller set of constraints to be
+[KeepConstraint.LOOKUP](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#LOOKUP) since we must find the fields via `Class.getDeclaredFields` as well as
+[KeepConstraint.VISIBILITY_RELAX](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#VISIBILITY_RELAX) and [KeepConstraint.FIELD_GET](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#FIELD_GET) in order to be able to get
+the actual field value without accessibility errors.
+
+The effect is that the default constraint [KeepConstraint.NAME](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/KeepConstraint.html#NAME) is not specified which allows
+the shrinker to rename the fields at will.
+
+
+```
+public class MyAnnotationPrinter {
+
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MyNameAnnotation {
+ String value();
+ }
+
+ public static class MyClass {
+ @MyNameAnnotation("fieldOne")
+ public int mFieldOne = 1;
+
+ @MyNameAnnotation("fieldTwo")
+ public int mFieldTwo = 2;
+
+ public int mFieldThree = 3;
+ }
+
+ @UsesReflection(
+ @KeepTarget(
+ fieldAnnotatedByClassConstant = MyNameAnnotation.class,
+ constrainAnnotations = @AnnotationPattern(constant = MyNameAnnotation.class),
+ constraints = {
+ KeepConstraint.LOOKUP,
+ KeepConstraint.VISIBILITY_RELAX,
+ KeepConstraint.FIELD_GET
+ }))
+ public void printMyNameAnnotatedFields(Object obj) throws Exception {
+ for (Field field : obj.getClass().getDeclaredFields()) {
+ if (field.isAnnotationPresent(MyNameAnnotation.class)) {
+ System.out.println(
+ field.getAnnotation(MyNameAnnotation.class).value() + " = " + field.get(obj));
+ }
+ }
+ }
+}
+```
+
+
+If the annotations that need to be kept are not runtime
+visible annotations, then you must specify that by including the `RetentionPolicy.CLASS` value in the
+[@AnnotationPattern.retention](https://storage.googleapis.com/r8-releases/raw/main/docs/keepanno/javadoc/com/android/tools/r8/keepanno/annotations/AnnotationPattern.html#retention()) property.
+An annotation is runtime visible if its definition is explicitly annotated with
+`Retention(RetentionPolicy.RUNTIME)`.
+
+
## Annotating code used by reflection (or via JNI)<a name="used-by-reflection"></a>
diff --git a/doc/keepanno-guide.template.md b/doc/keepanno-guide.template.md
index 104bf02..54d910c 100644
--- a/doc/keepanno-guide.template.md
+++ b/doc/keepanno-guide.template.md
@@ -83,6 +83,19 @@
[[[INCLUDE CODE:UsesReflectionFieldPrinter]]]
+### [Accessing annotations](using-reflection-annotations)
+
+[[[INCLUDE DOC:UsesReflectionOnAnnotations]]]
+
+[[[INCLUDE CODE:UsesReflectionOnAnnotations]]]
+
+If the annotations that need to be kept are not runtime
+visible annotations, then you must specify that by including the `RetentionPolicy.CLASS` value in the
+`@AnnotationPattern#retention` property.
+An annotation is runtime visible if its definition is explicitly annotated with
+`Retention(RetentionPolicy.RUNTIME)`.
+
+
## [Annotating code used by reflection (or via JNI)](used-by-reflection)
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
new file mode 100644
index 0000000..cfd4302
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/AnnotationPattern.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2024, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A pattern structure for matching annotations.
+ *
+ * <p>If no properties are set, the default pattern matches any annotation with a runtime retention
+ * policy.
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface AnnotationPattern {
+
+ /**
+ * Define the annotation-name pattern by fully qualified class name.
+ *
+ * <p>Mutually exclusive with the following other properties defining annotation-name:
+ *
+ * <ul>
+ * <li>constant
+ * <li>namePattern
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any annotation name.
+ *
+ * @return The qualified class name that defines the annotation.
+ */
+ String name() default "";
+
+ /**
+ * Define the annotation-name pattern by reference to a {@code Class} constant.
+ *
+ * <p>Mutually exclusive with the following other properties defining annotation-name:
+ *
+ * <ul>
+ * <li>name
+ * <li>namePattern
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any annotation name.
+ *
+ * @return The Class constant that defines the annotation.
+ */
+ Class<?> constant() default Object.class;
+
+ /**
+ * Define the annotation-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining annotation-name:
+ *
+ * <ul>
+ * <li>name
+ * <li>constant
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any annotation name.
+ *
+ * @return The class-name pattern that defines the annotation.
+ */
+ ClassNamePattern namePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
+ * Specify which retention policies must be set for the annotations.
+ *
+ * <p>Matches annotations with matching retention policies
+ *
+ * @return Retention policies. By default {@code RetentionPolicy.RUNTIME}.
+ */
+ RetentionPolicy[] retention() default {RetentionPolicy.RUNTIME};
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java
index 8cfa99d..bd6f7c5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstraint.java
@@ -209,12 +209,4 @@
* non-visible uses requires the same annotations to preserve as for reflective uses.
*/
CLASS_OPEN_HIERARCHY,
-
- /**
- * Indicates that the annotations on the target item are being accessed reflectively.
- *
- * <p>If only a particular set of annotations is accessed, you should set the TBD property on the
- * target item.
- */
- ANNOTATIONS,
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index bf9251b..20e5492 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -110,6 +110,22 @@
KeepConstraint[] constraintAdditions() default {};
/**
+ * Patterns for annotations that must remain on the item.
+ *
+ * <p>The annotations matching any of the patterns must remain on the item if the annotation types
+ * remain in the program.
+ *
+ * <p>Note that if the annotation types themselves are unused/removed, then their references on
+ * the item will be removed too. If the annotation types themselves are used reflectively then
+ * they too need a keep annotation or rule to ensure they remain in the program.
+ *
+ * <p>By default no annotation patterns are defined and no annotations are required to remain.
+ *
+ * @return Annotation patterns
+ */
+ AnnotationPattern[] constrainAnnotations() default {};
+
+ /**
* Define the class pattern by reference to a binding.
*
* <p>Mutually exclusive with the following other properties defining class:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
index 6157cbd..076a93c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByNative.java
@@ -132,6 +132,22 @@
KeepConstraint[] constraintAdditions() default {};
/**
+ * Patterns for annotations that must remain on the item.
+ *
+ * <p>The annotations matching any of the patterns must remain on the item if the annotation types
+ * remain in the program.
+ *
+ * <p>Note that if the annotation types themselves are unused/removed, then their references on
+ * the item will be removed too. If the annotation types themselves are used reflectively then
+ * they too need a keep annotation or rule to ensure they remain in the program.
+ *
+ * <p>By default no annotation patterns are defined and no annotations are required to remain.
+ *
+ * @return Annotation patterns
+ */
+ AnnotationPattern[] constrainAnnotations() default {};
+
+ /**
* Define the member-annotated-by pattern by fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining member-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
index 17add2d..cf2225d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/UsedByReflection.java
@@ -132,6 +132,22 @@
KeepConstraint[] constraintAdditions() default {};
/**
+ * Patterns for annotations that must remain on the item.
+ *
+ * <p>The annotations matching any of the patterns must remain on the item if the annotation types
+ * remain in the program.
+ *
+ * <p>Note that if the annotation types themselves are unused/removed, then their references on
+ * the item will be removed too. If the annotation types themselves are used reflectively then
+ * they too need a keep annotation or rule to ensure they remain in the program.
+ *
+ * <p>By default no annotation patterns are defined and no annotations are required to remain.
+ *
+ * @return Annotation patterns
+ */
+ AnnotationPattern[] constrainAnnotations() default {};
+
+ /**
* Define the member-annotated-by pattern by fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining member-annotated-by:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationPatternParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationPatternParser.java
new file mode 100644
index 0000000..f10f9cd
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationPatternParser.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2024, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.AnnotationPattern;
+import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
+import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.google.common.collect.ImmutableList;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+public class AnnotationPatternParser
+ extends PropertyParserBase<KeepAnnotationPattern, AnnotationPatternParser.AnnotationProperty> {
+
+ public enum AnnotationProperty {
+ PATTERN
+ }
+
+ public AnnotationPatternParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ @Override
+ AnnotationVisitor tryPropertyAnnotation(
+ AnnotationProperty property,
+ String name,
+ String descriptor,
+ Consumer<KeepAnnotationPattern> setValue) {
+ switch (property) {
+ case PATTERN:
+ AnnotationParsingContext parsingContext =
+ getParsingContext().property(name).annotation(descriptor);
+ AnnotationDeclarationParser parser = new AnnotationDeclarationParser(parsingContext);
+ return new ParserVisitor(
+ parsingContext,
+ parser,
+ () ->
+ setValue.accept(
+ parser.isDeclared()
+ ? parser.getValue()
+ : KeepAnnotationPattern.anyWithRuntimeRetention()));
+ default:
+ return super.tryPropertyAnnotation(property, name, descriptor, setValue);
+ }
+ }
+
+ private enum RetentionProperty {
+ RETENTION
+ }
+
+ private static class RetentionParser
+ extends PropertyParserBase<RetentionPolicy, RetentionProperty> {
+
+ private static final String RETENTION_POLICY_DESC = "Ljava/lang/annotation/RetentionPolicy;";
+
+ public RetentionParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ @Override
+ public boolean tryPropertyEnum(
+ RetentionProperty property,
+ String name,
+ String descriptor,
+ String value,
+ Consumer<RetentionPolicy> setValue) {
+ assert property == RetentionProperty.RETENTION;
+ if (RETENTION_POLICY_DESC.equals(descriptor)) {
+ setValue.accept(RetentionPolicy.valueOf(value));
+ return true;
+ }
+ return super.tryPropertyEnum(property, name, descriptor, value, setValue);
+ }
+ }
+
+ private static class AnnotationDeclarationParser
+ extends DeclarationParser<KeepAnnotationPattern> {
+
+ private final ClassNameParser nameParser;
+ private final ArrayPropertyParser<RetentionPolicy, RetentionProperty> retentionParser;
+ private final List<Parser<?>> parsers;
+
+ public AnnotationDeclarationParser(ParsingContext parsingContext) {
+ nameParser = new ClassNameParser(parsingContext);
+ nameParser.setProperty(AnnotationPattern.name, ClassNameProperty.NAME);
+ nameParser.setProperty(AnnotationPattern.constant, ClassNameProperty.CONSTANT);
+ nameParser.setProperty(AnnotationPattern.namePattern, ClassNameProperty.PATTERN);
+ retentionParser = new ArrayPropertyParser<>(parsingContext, RetentionParser::new);
+ retentionParser.setProperty(AnnotationPattern.retention, RetentionProperty.RETENTION);
+ retentionParser.setValueCheck(
+ (value, propertyContext) -> {
+ if (value.isEmpty()) {
+ throw propertyContext.error("Expected non-empty array of retention policies");
+ }
+ });
+ parsers = ImmutableList.of(nameParser, retentionParser);
+ }
+
+ @Override
+ List<Parser<?>> parsers() {
+ return parsers;
+ }
+
+ public KeepAnnotationPattern getValue() {
+ if (isDefault()) {
+ return null;
+ }
+ KeepAnnotationPattern.Builder builder = KeepAnnotationPattern.builder();
+ if (retentionParser.isDeclared()) {
+ List<RetentionPolicy> policies = retentionParser.getValue();
+ policies.forEach(builder::addRetentionPolicy);
+ } else {
+ builder.addRetentionPolicy(RetentionPolicy.RUNTIME);
+ }
+ return builder
+ .setNamePattern(nameParser.getValueOrDefault(KeepQualifiedClassNamePattern.any()))
+ .build();
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java
index 3c6cd08..3ae1bd2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ArrayPropertyParser.java
@@ -26,6 +26,7 @@
AnnotationVisitor tryPropertyArray(P property, String name, Consumer<List<T>> setValue) {
// The property name and type is forwarded to the element parser.
values = new ArrayList<>();
+ // The context is explicitly *not* extended with the property name here as it is forwarded.
ParsingContext parsingContext = getParsingContext();
return new AnnotationVisitorBase(parsingContext) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index 9137b3e..6bc17aa 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -89,7 +89,6 @@
nameParser.setProperty(ClassNamePattern.simpleName, ClassSimpleNameProperty.NAME);
return new ParserVisitor(
parsingContext,
- descriptor,
ImmutableList.of(packageParser, nameParser),
() ->
setValue.accept(
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintPropertiesParser.java
similarity index 81%
rename from src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java
rename to src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintPropertiesParser.java
index e89279b..c652e91 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintsParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ConstraintPropertiesParser.java
@@ -4,21 +4,22 @@
package com.android.tools.r8.keepanno.asm;
-import com.android.tools.r8.keepanno.asm.ConstraintsParser.ConstraintsProperty;
+import com.android.tools.r8.keepanno.asm.ConstraintPropertiesParser.ConstraintsProperty;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.KeepConstraints;
import com.android.tools.r8.keepanno.ast.ParsingContext;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
-public class ConstraintsParser extends PropertyParserBase<KeepConstraints, ConstraintsProperty> {
+public class ConstraintPropertiesParser
+ extends PropertyParserBase<KeepConstraints, ConstraintsProperty> {
public enum ConstraintsProperty {
CONSTRAINTS,
ADDITIONS
}
- public ConstraintsParser(ParsingContext parsingContext) {
+ public ConstraintPropertiesParser(ParsingContext parsingContext) {
super(parsingContext.group(Target.constraintsGroup));
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
index b0b58f8..33bf5be 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepConstraintsVisitor.java
@@ -67,9 +67,6 @@
case Constraints.CLASS_OPEN_HIERARCHY:
builder.add(KeepConstraint.classOpenHierarchy());
break;
- case Constraints.ANNOTATIONS:
- builder.add(KeepConstraint.annotationsAll());
- break;
default:
super.visitEnum(ignore, descriptor, value);
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 8c85b9a..1c6c71e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.keepanno.asm;
import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
-import com.android.tools.r8.keepanno.asm.ConstraintsParser.ConstraintsProperty;
+import com.android.tools.r8.keepanno.asm.ConstraintPropertiesParser.ConstraintsProperty;
import com.android.tools.r8.keepanno.asm.InstanceOfParser.InstanceOfProperties;
import com.android.tools.r8.keepanno.asm.StringPatternParser.StringProperty;
import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
@@ -21,6 +21,7 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.MethodAccess;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
+import com.android.tools.r8.keepanno.ast.KeepAnnotationPattern;
import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
@@ -30,6 +31,7 @@
import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConstraint;
import com.android.tools.r8.keepanno.ast.KeepConstraints;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.ast.KeepEdge;
@@ -61,6 +63,7 @@
import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -521,16 +524,17 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.bindings)) {
- return new KeepBindingsVisitor(parsingContext, bindingsHelper);
+ return new KeepBindingsVisitor(propertyParsingContext, bindingsHelper);
}
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext, builder::setPreconditions, bindingsHelper);
+ propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(Edge.consequences)) {
return new KeepConsequencesVisitor(
- parsingContext, builder::setConsequences, bindingsHelper);
+ propertyParsingContext, builder::setConsequences, bindingsHelper);
}
return super.visitArray(name);
}
@@ -593,7 +597,7 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ parsingContext.property(name),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -695,7 +699,7 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ parsingContext.property(name),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -715,6 +719,64 @@
}
}
+ public static class ConstraintDeclarationParser extends DeclarationParser<KeepConstraints> {
+ private final ConstraintPropertiesParser constraintsParser;
+ private final ArrayPropertyParser<
+ KeepAnnotationPattern, AnnotationPatternParser.AnnotationProperty>
+ annotationsParser;
+
+ public ConstraintDeclarationParser(ParsingContext parsingContext) {
+ constraintsParser = new ConstraintPropertiesParser(parsingContext);
+ constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
+ constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
+
+ annotationsParser = new ArrayPropertyParser<>(parsingContext, AnnotationPatternParser::new);
+ annotationsParser.setProperty(
+ Target.constrainAnnotations, AnnotationPatternParser.AnnotationProperty.PATTERN);
+ annotationsParser.setValueCheck(this::verifyAnnotationList);
+ }
+
+ private void verifyAnnotationList(
+ List<KeepAnnotationPattern> annotationList, ParsingContext parsingContext) {
+ if (annotationList.isEmpty()) {
+ throw parsingContext.error("Expected non-empty array of annotation patterns");
+ }
+ }
+
+ @Override
+ List<Parser<?>> parsers() {
+ return ImmutableList.of(constraintsParser, annotationsParser);
+ }
+
+ public KeepConstraints getValueOrDefault(KeepConstraints defaultValue) {
+ return isDeclared() ? getValue() : defaultValue;
+ }
+
+ public KeepConstraints getValue() {
+ if (isDefault()) {
+ return null;
+ }
+ // If only the constraints are set then those are the constraints as is.
+ if (annotationsParser.isDefault()) {
+ assert constraintsParser.isDeclared();
+ return constraintsParser.getValue();
+ }
+ KeepConstraints.Builder builder;
+ if (constraintsParser.isDeclared()) {
+ // If constraints are set use it as the initial set.
+ builder = KeepConstraints.builder().copyFrom(constraintsParser.getValue());
+ assert builder.verifyNoAnnotations();
+ } else {
+ // If only the annotations are set, add them as an extension of the defaults.
+ builder = KeepConstraints.builder().copyFrom(KeepConstraints.defaultConstraints());
+ }
+ annotationsParser
+ .getValue()
+ .forEach(pattern -> builder.add(KeepConstraint.annotation(pattern)));
+ return builder.build();
+ }
+ }
+
/**
* Parsing of @UsedByReflection or @UsedByNative on a class context.
*
@@ -731,7 +793,7 @@
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
- private final ConstraintsParser constraintsParser;
+ private final ConstraintDeclarationParser constraintsParser;
UsedByReflectionClassVisitor(
AnnotationParsingContext parsingContext,
@@ -743,11 +805,9 @@
this.className = className;
this.parent = parent;
addContext.accept(metaInfoBuilder);
+ constraintsParser = new ConstraintDeclarationParser(parsingContext);
// The class context/holder is the annotated class.
visit(Item.className, className);
- constraintsParser = new ConstraintsParser(parsingContext);
- constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
- constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
}
@Override
@@ -766,13 +826,14 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext, builder::setPreconditions, bindingsHelper);
+ propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ propertyParsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -842,7 +903,7 @@
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private ItemKind kind = KeepEdgeReader.ItemKind.ONLY_MEMBERS;
- private final ConstraintsParser constraintsParser;
+ private final ConstraintDeclarationParser constraintsParser;
UsedByReflectionMemberVisitor(
AnnotationParsingContext parsingContext,
@@ -854,9 +915,7 @@
this.parent = parent;
this.context = context;
addContext.accept(metaInfoBuilder);
- constraintsParser = new ConstraintsParser(parsingContext);
- constraintsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
- constraintsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
+ constraintsParser = new ConstraintDeclarationParser(parsingContext);
}
@Override
@@ -883,13 +942,14 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext, builder::setPreconditions, bindingsHelper);
+ propertyParsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
- parsingContext,
+ propertyParsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -973,13 +1033,14 @@
@Override
public AnnotationVisitor visitArray(String name) {
+ PropertyParsingContext propertyParsingContext = parsingContext.property(name);
if (name.equals(AnnotationConstants.UsesReflection.value)) {
return new KeepConsequencesVisitor(
- parsingContext, builder::setConsequences, bindingsHelper);
+ propertyParsingContext, builder::setConsequences, bindingsHelper);
}
if (name.equals(AnnotationConstants.UsesReflection.additionalPreconditions)) {
return new KeepPreconditionsVisitor(
- parsingContext,
+ propertyParsingContext,
additionalPreconditions -> {
additionalPreconditions.forEach(preconditions::addCondition);
},
@@ -1003,7 +1064,7 @@
private final ParsingContext parsingContext;
private final UserBindingsHelper helper;
- public KeepBindingsVisitor(ParsingContext parsingContext, UserBindingsHelper helper) {
+ public KeepBindingsVisitor(PropertyParsingContext parsingContext, UserBindingsHelper helper) {
super(parsingContext);
this.parsingContext = parsingContext;
this.helper = helper;
@@ -1011,6 +1072,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
if (descriptor.equals(AnnotationConstants.Binding.DESCRIPTOR)) {
return new KeepBindingVisitor(parsingContext.annotation(descriptor), helper);
}
@@ -1025,7 +1087,7 @@
private final UserBindingsHelper bindingsHelper;
public KeepPreconditionsVisitor(
- ParsingContext parsingContext,
+ PropertyParsingContext parsingContext,
Parent<KeepPreconditions> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
@@ -1036,6 +1098,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
if (descriptor.equals(Condition.DESCRIPTOR)) {
return new KeepConditionVisitor(
parsingContext.annotation(descriptor), builder::addCondition, bindingsHelper);
@@ -1056,7 +1119,7 @@
private final UserBindingsHelper bindingsHelper;
public KeepConsequencesVisitor(
- ParsingContext parsingContext,
+ PropertyParsingContext parsingContext,
Parent<KeepConsequences> parent,
UserBindingsHelper bindingsHelper) {
super(parsingContext);
@@ -1067,6 +1130,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ assert name == null;
if (descriptor.equals(Target.DESCRIPTOR)) {
return KeepTargetVisitor.create(
parsingContext.annotation(descriptor), builder::addTarget, bindingsHelper);
@@ -1786,7 +1850,7 @@
private final Parent<KeepTarget> parent;
private final UserBindingsHelper bindingsHelper;
- private final ConstraintsParser optionsParser;
+ private final ConstraintDeclarationParser constraintsParser;
private final KeepTarget.Builder builder = KeepTarget.builder();
static KeepTargetVisitor create(
@@ -1803,9 +1867,7 @@
super(parsingContext);
this.parent = parent;
this.bindingsHelper = bindingsHelper;
- optionsParser = new ConstraintsParser(parsingContext);
- optionsParser.setProperty(Target.constraints, ConstraintsProperty.CONSTRAINTS);
- optionsParser.setProperty(Target.constraintAdditions, ConstraintsProperty.ADDITIONS);
+ constraintsParser = new ConstraintDeclarationParser(parsingContext);
}
@Override
@@ -1815,7 +1877,7 @@
@Override
public AnnotationVisitor visitArray(String name) {
- AnnotationVisitor visitor = optionsParser.tryParseArray(name, unused -> {});
+ AnnotationVisitor visitor = constraintsParser.tryParseArray(name, unused -> {});
if (visitor != null) {
return visitor;
}
@@ -1825,7 +1887,8 @@
@Override
public void visitEnd() {
super.visitEnd();
- builder.setConstraints(optionsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
+ builder.setConstraints(
+ constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
for (KeepItemReference item : getItemsWithBinding()) {
parent.accept(builder.setItemReference(item).build());
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
index 5f1f69b..5acb59f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
@@ -12,33 +12,26 @@
/** Convert parser(s) into an annotation visitor. */
public class ParserVisitor extends AnnotationVisitorBase {
- private final List<PropertyParser<?, ?>> parsers;
+ private final List<Parser<?>> parsers;
private final Runnable onVisitEnd;
public ParserVisitor(
- AnnotationParsingContext parsingContext,
- String annotationDescriptor,
- List<PropertyParser<?, ?>> parsers,
- Runnable onVisitEnd) {
+ AnnotationParsingContext parsingContext, List<Parser<?>> parsers, Runnable onVisitEnd) {
super(parsingContext);
this.parsers = parsers;
this.onVisitEnd = onVisitEnd;
- assert annotationDescriptor.equals(parsingContext.getAnnotationDescriptor());
}
public ParserVisitor(
- AnnotationParsingContext parsingContext,
- String annotationDescriptor,
- PropertyParser<?, ?> declaration,
- Runnable onVisitEnd) {
- this(parsingContext, annotationDescriptor, Collections.singletonList(declaration), onVisitEnd);
+ AnnotationParsingContext parsingContext, Parser<?> parser, Runnable onVisitEnd) {
+ this(parsingContext, Collections.singletonList(parser), onVisitEnd);
}
private <T> void ignore(T unused) {}
@Override
public void visit(String name, Object value) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
if (parser.tryParse(name, value, this::ignore)) {
return;
}
@@ -48,7 +41,7 @@
@Override
public AnnotationVisitor visitArray(String name) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
AnnotationVisitor visitor = parser.tryParseArray(name, this::ignore);
if (visitor != null) {
return visitor;
@@ -59,7 +52,7 @@
@Override
public void visitEnum(String name, String descriptor, String value) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
if (parser.tryParseEnum(name, descriptor, value, this::ignore)) {
return;
}
@@ -69,7 +62,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
- for (PropertyParser<?, ?> parser : parsers) {
+ for (Parser<?> parser : parsers) {
AnnotationVisitor visitor = parser.tryParseAnnotation(name, descriptor, this::ignore);
if (visitor != null) {
return visitor;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
index 0213ef3..f5b40c3 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.keepanno.ast.ParsingContext;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
@@ -18,6 +19,7 @@
private final Map<String, P> mapping = new HashMap<>();
private String resultPropertyName = null;
private T resultValue = null;
+ private BiConsumer<T, ParsingContext> check = null;
protected PropertyParserBase(ParsingContext parsingContext) {
this.parsingContext = parsingContext;
@@ -52,6 +54,9 @@
private Consumer<T> wrap(String propertyName, Consumer<T> setValue) {
return value -> {
assert value != null;
+ if (check != null) {
+ check.accept(value, parsingContext.property(propertyName));
+ }
if (resultPropertyName != null) {
assert resultValue != null;
error(propertyName);
@@ -144,4 +149,8 @@
}
return null;
}
+
+ public void setValueCheck(BiConsumer<T, ParsingContext> check) {
+ this.check = check;
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java
index f866a90..2bb95df 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/StringPatternParser.java
@@ -57,7 +57,6 @@
suffixParser.setProperty(StringPattern.endsWith, StringParser.Property.STRING);
return new ParserVisitor(
parsingContext,
- descriptor,
ImmutableList.of(exactParser, prefixParser, suffixParser),
() -> {
if (exactParser.isDeclared()) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
index a40f450..a0c0afc 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -58,7 +58,6 @@
typeParser.setProperty(TypePattern.classNamePattern, TypeProperty.CLASS_NAME_PATTERN);
return new ParserVisitor(
context,
- descriptor,
typeParser,
() -> setValue.accept(typeParser.getValueOrDefault(KeepTypePattern.any())));
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 9828d43..239dd0e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -139,6 +139,7 @@
public static final String constraintsGroup = "constraints";
public static final String constraints = "constraints";
public static final String constraintAdditions = "constraintAdditions";
+ public static final String constrainAnnotations = "constrainAnnotations";
}
public static final class Kind {
@@ -169,7 +170,6 @@
public static final String FIELD_REPLACE = "FIELD_REPLACE";
public static final String NEVER_INLINE = "NEVER_INLINE";
public static final String CLASS_OPEN_HIERARCHY = "CLASS_OPEN_HIERARCHY";
- public static final String ANNOTATIONS = "ANNOTATIONS";
}
public static final class Option {
@@ -236,4 +236,14 @@
public static final String simpleName = "simpleName";
public static final String packageName = "packageName";
}
+
+ public static final class AnnotationPattern {
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/AnnotationPattern;";
+ public static final String annotationNameGroup = "annotation-name";
+ public static final String name = "name";
+ public static final String constant = "constant";
+ public static final String namePattern = "namePattern";
+ public static final String retention = "retention";
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
new file mode 100644
index 0000000..5c7cf96
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationPattern.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2024, 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.keepanno.ast;
+
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+public class KeepAnnotationPattern {
+
+ private static final int RUNTIME_RETENTION_MASK = 0x1;
+ private static final int CLASS_RETENTION_MASK = 0x2;
+ private static final int ANY_RETENTION_MASK = 0x3;
+
+ private static final KeepAnnotationPattern ANY_WITH_ANY_RETENTION =
+ new KeepAnnotationPattern(KeepQualifiedClassNamePattern.any(), ANY_RETENTION_MASK);
+
+ private static final KeepAnnotationPattern ANY_WITH_RUNTIME_RETENTION =
+ new KeepAnnotationPattern(KeepQualifiedClassNamePattern.any(), RUNTIME_RETENTION_MASK);
+
+ private static final KeepAnnotationPattern ANY_WITH_CLASS_RETENTION =
+ new KeepAnnotationPattern(KeepQualifiedClassNamePattern.any(), CLASS_RETENTION_MASK);
+
+ public static KeepAnnotationPattern any() {
+ return KeepAnnotationPattern.ANY_WITH_ANY_RETENTION;
+ }
+
+ public static KeepAnnotationPattern anyWithRuntimeRetention() {
+ return KeepAnnotationPattern.ANY_WITH_RUNTIME_RETENTION;
+ }
+
+ public static KeepAnnotationPattern anyWithClassRetention() {
+ return KeepAnnotationPattern.ANY_WITH_CLASS_RETENTION;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepQualifiedClassNamePattern namePattern = KeepQualifiedClassNamePattern.any();
+ private int retentionPolicies = 0x0;
+
+ private Builder() {}
+
+ public Builder setNamePattern(KeepQualifiedClassNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return this;
+ }
+
+ public Builder addRetentionPolicy(RetentionPolicy policy) {
+ switch (policy) {
+ case RUNTIME:
+ retentionPolicies |= RUNTIME_RETENTION_MASK;
+ break;
+ case CLASS:
+ retentionPolicies |= CLASS_RETENTION_MASK;
+ break;
+ case SOURCE:
+ throw new KeepEdgeException("Retention policy SOURCE cannot be used in patterns");
+ default:
+ throw new KeepEdgeException("Invalid policy: " + policy);
+ }
+ return this;
+ }
+
+ public KeepAnnotationPattern build() {
+ if (retentionPolicies == 0x0) {
+ throw new KeepEdgeException("Invalid empty retention policy");
+ }
+ if (namePattern.isAny()) {
+ switch (retentionPolicies) {
+ case RUNTIME_RETENTION_MASK:
+ return ANY_WITH_RUNTIME_RETENTION;
+ case CLASS_RETENTION_MASK:
+ return ANY_WITH_CLASS_RETENTION;
+ case ANY_RETENTION_MASK:
+ return ANY_WITH_ANY_RETENTION;
+ default:
+ throw new KeepEdgeException("Invalid retention policy value: " + retentionPolicies);
+ }
+ }
+ return new KeepAnnotationPattern(namePattern, retentionPolicies);
+ }
+ }
+
+ private final KeepQualifiedClassNamePattern namePattern;
+ private final int retentionPolicies;
+
+ private KeepAnnotationPattern(KeepQualifiedClassNamePattern namePattern, int retentionPolicies) {
+ assert namePattern != null;
+ this.namePattern = namePattern;
+ this.retentionPolicies = retentionPolicies;
+ }
+
+ public boolean isAny() {
+ return this == ANY_WITH_ANY_RETENTION;
+ }
+
+ public boolean isAnyWithRuntimeRetention() {
+ return this == ANY_WITH_RUNTIME_RETENTION;
+ }
+
+ public boolean isAnyWithClassRetention() {
+ return this == ANY_WITH_CLASS_RETENTION;
+ }
+
+ public KeepQualifiedClassNamePattern getNamePattern() {
+ return namePattern;
+ }
+
+ public boolean includesRuntimeRetention() {
+ return (retentionPolicies & RUNTIME_RETENTION_MASK) > 0;
+ }
+
+ public boolean includesClassRetention() {
+ return (retentionPolicies & CLASS_RETENTION_MASK) > 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof KeepAnnotationPattern)) {
+ return false;
+ }
+ KeepAnnotationPattern that = (KeepAnnotationPattern) o;
+ return retentionPolicies == that.retentionPolicies && namePattern.equals(that.namePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(namePattern, retentionPolicies);
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
index 0f81deb..454f725 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraint.java
@@ -286,23 +286,42 @@
return Annotation.ALL_INSTANCE;
}
- public static Annotation annotation(KeepQualifiedClassNamePattern pattern) {
+ public static Annotation annotationsAllWithRuntimeRetention() {
+ return Annotation.ALL_WITH_RUNTIME_RETENTION_INSTANCE;
+ }
+
+ public static Annotation annotationsAllWithClassRetention() {
+ return Annotation.ALL_WITH_CLASS_RETENTION_INSTANCE;
+ }
+
+ public static Annotation annotation(KeepAnnotationPattern pattern) {
if (pattern.isAny()) {
return annotationsAll();
}
+ if (pattern.isAnyWithRuntimeRetention()) {
+ return annotationsAllWithRuntimeRetention();
+ }
+ if (pattern.isAnyWithClassRetention()) {
+ return annotationsAllWithClassRetention();
+ }
return new Annotation(pattern);
}
public static final class Annotation extends KeepConstraint {
- private static final Annotation ALL_INSTANCE =
- new Annotation(KeepQualifiedClassNamePattern.any());
+ private static final Annotation ALL_INSTANCE = new Annotation(KeepAnnotationPattern.any());
- private final KeepQualifiedClassNamePattern classNamePattern;
+ private static final Annotation ALL_WITH_RUNTIME_RETENTION_INSTANCE =
+ new Annotation(KeepAnnotationPattern.anyWithRuntimeRetention());
- private Annotation(KeepQualifiedClassNamePattern classNamePattern) {
- assert classNamePattern != null;
- this.classNamePattern = classNamePattern;
+ private static final Annotation ALL_WITH_CLASS_RETENTION_INSTANCE =
+ new Annotation(KeepAnnotationPattern.anyWithClassRetention());
+
+ private final KeepAnnotationPattern annotationPattern;
+
+ private Annotation(KeepAnnotationPattern annotationPattern) {
+ assert annotationPattern != null;
+ this.annotationPattern = annotationPattern;
}
@Override
@@ -315,12 +334,16 @@
@Override
public void addRequiredKeepAttributes(Set<KeepAttribute> attributes) {
- attributes.add(KeepAttribute.RUNTIME_VISIBLE_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_INVISIBLE_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
- attributes.add(KeepAttribute.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ if (annotationPattern.includesRuntimeRetention()) {
+ attributes.add(KeepAttribute.RUNTIME_VISIBLE_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+ }
+ if (annotationPattern.includesClassRetention()) {
+ attributes.add(KeepAttribute.RUNTIME_INVISIBLE_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS);
+ attributes.add(KeepAttribute.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+ }
}
@Override
@@ -332,12 +355,12 @@
return false;
}
Annotation that = (Annotation) o;
- return classNamePattern.equals(that.classNamePattern);
+ return annotationPattern.equals(that.annotationPattern);
}
@Override
public int hashCode() {
- return classNamePattern.hashCode();
+ return annotationPattern.hashCode();
}
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
index c319a3c..eb5cc2e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConstraints.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.keepanno.ast;
+import com.android.tools.r8.keepanno.ast.KeepConstraint.Annotation;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
@@ -19,7 +20,12 @@
}
public static KeepConstraints defaultAdditions(KeepConstraints additionalConstraints) {
- return new Additions(additionalConstraints);
+ if (additionalConstraints instanceof Constraints) {
+ return new Additions((Constraints) additionalConstraints);
+ }
+ // If no explicit constraints are set, this is just identity on the defaults/additions.
+ assert additionalConstraints instanceof Defaults || additionalConstraints instanceof Additions;
+ return additionalConstraints;
}
public static Builder builder() {
@@ -28,17 +34,39 @@
public static class Builder {
+ private boolean defaultAdditions = false;
private final Set<KeepConstraint> constraints = new HashSet<>();
private Builder() {}
+ public Builder copyFrom(KeepConstraints fromConstraints) {
+ if (fromConstraints instanceof Defaults) {
+ // This builder is based on defaults so set the builder as an addition.
+ defaultAdditions = true;
+ } else if (fromConstraints instanceof Additions) {
+ // This builder is an addition, populate the additions into the constraint set.
+ defaultAdditions = true;
+ constraints.addAll(((Additions) fromConstraints).additions.constraints);
+ } else {
+ assert fromConstraints instanceof Constraints;
+ constraints.addAll(((Constraints) fromConstraints).constraints);
+ }
+ return this;
+ }
+
+ public boolean verifyNoAnnotations() {
+ assert constraints.stream().noneMatch(constraint -> constraint instanceof Annotation);
+ return true;
+ }
+
public Builder add(KeepConstraint constraint) {
constraints.add(constraint);
return this;
}
public KeepConstraints build() {
- return new Constraints(constraints);
+ Constraints constraintCollection = new Constraints(constraints);
+ return defaultAdditions ? new Additions(constraintCollection) : constraintCollection;
}
}
@@ -65,9 +93,9 @@
private static class Additions extends KeepConstraints {
- private final KeepConstraints additions;
+ private final Constraints additions;
- public Additions(KeepConstraints additions) {
+ public Additions(Constraints additions) {
this.additions = additions;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index b894eae..05ee81c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -122,6 +122,12 @@
*
* MEMBER_ACCESS_FLAG
* ::= public | protected | package-private | private | static | final | synthetic
+ *
+ * ANNOTATION_PATTERN
+ * ::= @QUALIFIED_CLASS_NAME_PATTERN retention(RETENTION_POLICY+)
+ *
+ * RETENTION_POLICY
+ * ::= RUNTIME | CLASS
* </pre>
*/
public final class KeepEdge extends KeepDeclaration {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
index 0d83a25..5f6bb55 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
@@ -55,6 +55,7 @@
}
public PropertyParsingContext property(String propertyName) {
+ assert propertyName != null;
return new PropertyParsingContext(this, propertyName);
}
@@ -273,5 +274,11 @@
public String getContextFrameAsString() {
return getPropertyName();
}
+
+ @Override
+ public boolean isSynthetic() {
+ // The value property is the default and usually unnamed property, so we avoid printing it.
+ return "value".equals(propertyName);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 33b687b..766371d 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -455,11 +455,11 @@
reporter.error(
"Option --main-dex-list-output requires --main-dex-rules and/or --main-dex-list");
}
- if (getMinApiLevel() >= AndroidApiLevel.L.getLevel()) {
+ if (getMinApiLevel() >= AndroidApiLevel.L_MR1.getLevel()) {
if (getMainDexListConsumer() != null || getAppBuilder().hasMainDexList()) {
reporter.error(
"D8 does not support main-dex inputs and outputs when compiling to API level "
- + AndroidApiLevel.L.getLevel()
+ + AndroidApiLevel.L_MR1.getLevel()
+ " and above");
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4507e1e..24f0c28 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -37,6 +37,7 @@
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.LirConverter;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
@@ -54,6 +55,7 @@
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.info.OptimizationInfoRemover;
import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.keepanno.annotations.KeepForApi;
@@ -508,7 +510,7 @@
initialRuntimeTypeCheckInfoBuilder.build(appView.graphLens()));
// TODO(b/225838009): Horizontal merging currently assumes pre-phase CF conversion.
- appView.testing().enterLirSupportedPhase(appView, executorService);
+ LirConverter.enterLirSupportedPhase(appView, executorService);
new ProtoNormalizer(appViewWithLiveness).run(executorService, timing);
@@ -532,6 +534,7 @@
new PrimaryR8IRConverter(appViewWithLiveness, timing)
.optimize(appViewWithLiveness, executorService);
+ assert LirConverter.verifyLirOnly(appView);
assert ArtProfileCompletenessChecker.verify(
appView, ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS);
@@ -715,14 +718,16 @@
SyntheticFinalization.finalizeWithClassHierarchy(appView, executorService, timing);
}
- // TODO(b/225838009): Move further down.
- PrimaryR8IRConverter.finalizeLirToOutputFormat(appView, timing, executorService);
-
// Read any -applymapping input to allow for repackaging to not relocate the classes.
timing.begin("read -applymapping file");
appView.loadApplyMappingSeedMapper();
timing.end();
+ // Remove optimization info before remaining optimizations, since these optimization currently
+ // do not rewrite the optimization info, which is OK since the optimization info should
+ // already have been leveraged.
+ OptimizationInfoRemover.run(appView, executorService);
+
// Perform repackaging.
if (appView.hasLiveness()) {
if (options.isRepackagingEnabled()) {
@@ -731,18 +736,21 @@
assert Repackaging.verifyIdentityRepackaging(appView.withLiveness(), executorService);
}
- // Clear the reference type lattice element cache. This is required since class merging may
- // need to build IR.
- appView.dexItemFactory().clearTypeElementsCache();
-
- GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging =
- GenericSignatureContextBuilder.create(appView);
+ // Rewrite LIR with lens to allow building IR from LIR in class mergers.
+ LirConverter.rewriteLirWithLens(appView, timing, executorService);
if (appView.hasLiveness()) {
VerticalClassMerger.createForFinalClassMerging(appView.withLiveness())
.runIfNecessary(executorService, timing);
}
+ // TODO(b/225838009): Move further down.
+ LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
+ assert appView.dexItemFactory().verifyNoCachedTypeElements();
+
+ GenericSignatureContextBuilder genericContextBuilderBeforeFinalMerging =
+ GenericSignatureContextBuilder.create(appView);
+
// Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
// are always merged.
HorizontalClassMerger.createForFinalClassMerging(appView)
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 47f7ed6..e3d2f78 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -229,7 +229,9 @@
if (!options.isGeneratingDex()) {
return true;
}
- AndroidApiLevel nativeMultiDex = AndroidApiLevel.L;
+ // Native multidex is supported from L, but the compiler supports compiling to L/21 using
+ // legacy multidex as there are some devices that have issues with it still.
+ AndroidApiLevel nativeMultiDex = AndroidApiLevel.L_MR1;
if (options.getMinApiLevel().isLessThan(nativeMultiDex)) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
index a30eb47..b8e779d 100644
--- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -86,6 +86,8 @@
abstract AbstractAccessContexts rewrittenWithLens(
DexDefinitionSupplier definitions, GraphLens lens);
+ abstract AbstractAccessContexts withoutPrunedItems(PrunedItems prunedItems);
+
public static EmptyAccessContexts empty() {
return EmptyAccessContexts.getInstance();
}
@@ -155,6 +157,11 @@
public AbstractAccessContexts join(AbstractAccessContexts contexts) {
return contexts;
}
+
+ @Override
+ AbstractAccessContexts withoutPrunedItems(PrunedItems prunedItems) {
+ return this;
+ }
}
public static class ConcreteAccessContexts extends AbstractAccessContexts {
@@ -378,6 +385,14 @@
contexts.asConcrete().accessesWithContexts.forEach(addAllMethods);
return new ConcreteAccessContexts(newAccessesWithContexts);
}
+
+ @Override
+ AbstractAccessContexts withoutPrunedItems(PrunedItems prunedItems) {
+ for (ProgramMethodSet methodSet : accessesWithContexts.values()) {
+ methodSet.removeIf(method -> prunedItems.isRemoved(method.getReference()));
+ }
+ return this;
+ }
}
public static class UnknownAccessContexts extends AbstractAccessContexts {
@@ -440,5 +455,10 @@
public AbstractAccessContexts join(AbstractAccessContexts contexts) {
return this;
}
+
+ @Override
+ AbstractAccessContexts withoutPrunedItems(PrunedItems prunedItems) {
+ return this;
+ }
}
}
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 691680c..c09b0fd 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -101,6 +101,7 @@
private final WholeProgramOptimizations wholeProgramOptimizations;
private GraphLens codeLens = GraphLens.getIdentityLens();
private GraphLens graphLens = GraphLens.getIdentityLens();
+ private GraphLens genericSignaturesLens = GraphLens.getIdentityLens();
private InitClassLens initClassLens;
private GraphLens kotlinMetadataLens = GraphLens.getIdentityLens();
private NamingLens namingLens = NamingLens.getIdentityLens();
@@ -678,6 +679,14 @@
return false;
}
+ public GraphLens getGenericSignaturesLens() {
+ return genericSignaturesLens;
+ }
+
+ public void setGenericSignaturesLens(GraphLens genericSignaturesLens) {
+ this.genericSignaturesLens = genericSignaturesLens;
+ }
+
private boolean disallowFurtherInitClassUses = false;
public void dissallowFurtherInitClassUses() {
@@ -871,7 +880,6 @@
testing().verticallyMergedClassesConsumer.accept(dexItemFactory(), verticallyMergedClasses);
} else {
assert this.verticallyMergedClasses != null;
- assert verticallyMergedClasses.isEmpty();
}
}
@@ -1088,7 +1096,10 @@
public void run(Timing timing) {
if (appView.hasLiveness()) {
result =
- appView.appInfoWithLiveness().rewrittenWithLens(application, lens, timing);
+ appView
+ .appInfoWithLiveness()
+ .rewrittenWithLens(
+ application, lens, appliedLensInModifiedLens, timing);
} else {
assert appView.hasClassHierarchy();
AppView<AppInfoWithClassHierarchy> appViewWithClassHierarchy =
@@ -1246,6 +1257,23 @@
public boolean shouldRun() {
return !appView.getStartupProfile().isEmpty();
}
+ },
+ new ThreadTask() {
+ @Override
+ public void run(Timing timing) {
+ ImmutableSet.Builder<DexMethod> cfByteCodePassThroughBuilder =
+ ImmutableSet.builder();
+ for (DexMethod method : appView.cfByteCodePassThrough) {
+ cfByteCodePassThroughBuilder.add(
+ lens.getRenamedMethodSignature(method, appliedLensInModifiedLens));
+ }
+ appView.cfByteCodePassThrough = cfByteCodePassThroughBuilder.build();
+ }
+
+ @Override
+ public boolean shouldRun() {
+ return !appView.cfByteCodePassThrough.isEmpty();
+ }
});
});
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 665a472..f43f02e 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -19,17 +19,24 @@
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.conversion.SyntheticStraightLineSourceCode;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirEncodingStrategy;
+import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.IteratorUtils;
@@ -85,7 +92,12 @@
AppView<?> appView, ProgramMethod method, DexType superType) {
DexEncodedMethod definition = method.getDefinition();
assert definition.getCode().isDefaultInstanceInitializerCode();
- method.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView);
+ if (appView.testing().isSupportedLirPhase()) {
+ method.setCode(get().toLirCode(appView, method), appView);
+ } else {
+ assert appView.testing().isPreLirPhase();
+ method.setCode(get().toCfCode(method, appView.dexItemFactory(), superType), appView);
+ }
}
@Override
@@ -370,6 +382,27 @@
return new CfCode(method.getHolderType(), getMaxStack(), getMaxLocals(method), instructions);
}
+ public LirCode<?> toLirCode(AppView<?> appView, ProgramMethod method) {
+ TypeElement receiverType =
+ method.getHolder().getType().toTypeElement(appView, Nullability.definitelyNotNull());
+ Value receiver = new Value(0, receiverType, null);
+ DexMethod invokedMethod =
+ appView.dexItemFactory().createInstanceInitializer(method.getHolder().getSuperType());
+ LirEncodingStrategy<Value, Integer> strategy =
+ LirStrategy.getDefaultStrategy().getEncodingStrategy();
+ strategy.defineValue(receiver, 0);
+ return LirCode.builder(
+ method.getReference(),
+ method.getDefinition().isD8R8Synthesized(),
+ strategy,
+ appView.options())
+ .setMetadata(IRMetadata.unknown())
+ .addArgument(0, false)
+ .addInvokeDirect(invokedMethod, ImmutableList.of(receiver), false)
+ .addReturnVoid()
+ .build();
+ }
+
@Override
public void writeCf(
ProgramMethod method,
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 557bb44..af6a62f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -48,6 +48,10 @@
// Mapping from register to local for currently open/visible locals.
private Int2ReferenceMap<DebugLocalInfo> pendingLocals = null;
+ // The method's preamble position. This is used in release mode to preserve the preamble position
+ // for methods without throwing instructions that have been moved (i.e., have a caller position).
+ private Position preamblePosition;
+
// Conservative pending-state of locals to avoid some equality checks on locals.
// pendingLocalChanges == true ==> localsEqual(emittedLocals, pendingLocals).
private boolean pendingLocalChanges = false;
@@ -81,6 +85,9 @@
// Initialize locals state on block entry.
if (isBlockEntry) {
updateBlockEntry(instruction);
+ if (preamblePosition == null) {
+ preamblePosition = instruction.getPosition();
+ }
}
assert pendingLocals != null;
@@ -116,8 +123,17 @@
public DexDebugInfo build() {
assert pendingLocals == null;
assert !pendingLocalChanges;
+ assert preamblePosition != null;
if (startLine == NO_LINE_INFO) {
- return null;
+ if (!preamblePosition.hasCallerPosition()) {
+ return null;
+ }
+ return new EventBasedDebugInfo(
+ preamblePosition.getLine(),
+ new DexString[method.getReference().getArity()],
+ new DexDebugEvent[] {
+ factory.createPositionFrame(preamblePosition), factory.zeroChangeDefaultEvent
+ });
}
DexString[] params = new DexString[method.getReference().getArity()];
if (arguments != null) {
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 7465b0a..3c3533a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -10,6 +10,7 @@
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
import static java.util.Objects.requireNonNull;
@@ -30,18 +31,15 @@
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.dex.code.DexConstString;
-import com.android.tools.r8.dex.code.DexInstruction;
-import com.android.tools.r8.dex.code.DexInvokeDirect;
-import com.android.tools.r8.dex.code.DexInvokeStatic;
-import com.android.tools.r8.dex.code.DexNewInstance;
-import com.android.tools.r8.dex.code.DexThrow;
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.graph.lens.GraphLens;
import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.NestUtils;
@@ -52,6 +50,10 @@
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
+import com.android.tools.r8.lightir.LirBuilder;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirEncodingStrategy;
+import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.naming.NamingLens;
@@ -911,28 +913,6 @@
return getReference().toSourceString();
}
- /** Generates a {@link DexCode} object for the given instructions. */
- private DexCode generateCodeFromTemplate(
- int numberOfRegisters, int outRegisters, DexInstruction... instructions) {
- int offset = 0;
- for (DexInstruction instruction : instructions) {
- instruction.setOffset(offset);
- offset += instruction.getSize();
- }
- int requiredArgRegisters = accessFlags.isStatic() ? 0 : 1;
- for (DexType type : getReference().proto.parameters.values) {
- requiredArgRegisters += ValueType.fromDexType(type).requiredRegisters();
- }
- return new DexCode(
- Math.max(numberOfRegisters, requiredArgRegisters),
- requiredArgRegisters,
- outRegisters,
- instructions,
- new DexCode.Try[0],
- new DexCode.TryHandler[0],
- null);
- }
-
public CfCode buildInstanceOfCfCode(DexType type, boolean negate) {
CfInstruction[] instructions = new CfInstruction[3 + BooleanUtils.intValue(negate) * 2];
int i = 0;
@@ -950,13 +930,15 @@
Arrays.asList(instructions));
}
- public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
+ public DexEncodedMethod toMethodThatLogsError(
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
+ assert appView.testing().isPreLirPhase() || appView.testing().isSupportedLirPhase();
Builder builder =
builder(this)
.setCode(
- appView.options().isGeneratingClassFiles()
+ appView.testing().isPreLirPhase()
? toCfCodeThatLogsError(appView.dexItemFactory())
- : toDexCodeThatLogsError(appView.dexItemFactory()))
+ : toLirCodeThatLogsError(appView))
.setIsLibraryMethodOverrideIf(
belongsToVirtualPool() && !isLibraryMethodOverride().isUnknown(),
isLibraryMethodOverride())
@@ -979,50 +961,86 @@
}
}
- public static void setDebugInfoWithExtraParameters(
- Code code, int arity, int extraParameters, AppView<?> appView) {
- if (code.isDexCode()) {
- DexCode dexCode = code.asDexCode();
- DexDebugInfo newDebugInfo =
- dexCode.debugInfoWithExtraParameters(appView.dexItemFactory(), extraParameters);
- assert (newDebugInfo == null) || (arity == newDebugInfo.getParameterCount());
- dexCode.setDebugInfo(newDebugInfo);
- } else {
- assert code.isCfCode();
- // We don't have anything to do for Cf.
- }
- }
-
- private DexCode toDexCodeThatLogsError(DexItemFactory itemFactory) {
+ private LirCode<?> toLirCodeThatLogsError(AppView<? extends AppInfoWithClassHierarchy> appView) {
checkIfObsolete();
+ DexItemFactory factory = appView.dexItemFactory();
Signature signature = MethodSignature.fromDexMethod(getReference());
DexString message =
- itemFactory.createString(
+ factory.createString(
CONFIGURATION_DEBUGGING_PREFIX
+ getReference().holder.toSourceString()
+ ": "
+ signature);
- DexString tag = itemFactory.createString("[R8]");
- DexType[] args = {itemFactory.stringType, itemFactory.stringType};
- DexProto proto = itemFactory.createProto(itemFactory.intType, args);
- DexMethod logMethod =
- itemFactory.createMethod(
- itemFactory.androidUtilLogType, proto, itemFactory.createString("e"));
- DexType exceptionType = itemFactory.runtimeExceptionType;
+ DexString tag = factory.createString("[R8]");
+ DexType logger = factory.javaUtilLoggingLoggerType;
+ DexMethod getLogger =
+ factory.createMethod(
+ logger,
+ factory.createProto(logger, factory.stringType),
+ factory.createString("getLogger"));
+ DexMethod severe =
+ factory.createMethod(
+ logger,
+ factory.createProto(factory.voidType, factory.stringType),
+ factory.createString("severe"));
+ DexType exceptionType = factory.runtimeExceptionType;
DexMethod exceptionInitMethod =
- itemFactory.createMethod(
+ factory.createMethod(
exceptionType,
- itemFactory.createProto(itemFactory.voidType, itemFactory.stringType),
- itemFactory.constructorMethodName);
- return generateCodeFromTemplate(
- 2,
- 2,
- new DexConstString(0, tag),
- new DexConstString(1, message),
- new DexInvokeStatic(2, logMethod, 0, 1, 0, 0, 0),
- new DexNewInstance(0, exceptionType),
- new DexInvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0),
- new DexThrow(0));
+ factory.createProto(factory.voidType, factory.stringType),
+ factory.constructorMethodName);
+
+ TypeElement stringValueType = TypeElement.stringClassType(appView, definitelyNotNull());
+ TypeElement loggerValueType = logger.toTypeElement(appView);
+ TypeElement exceptionValueType = exceptionType.toTypeElement(appView, definitelyNotNull());
+
+ LirEncodingStrategy<Value, Integer> strategy =
+ LirStrategy.getDefaultStrategy().getEncodingStrategy();
+ LirBuilder<Value, Integer> lirBuilder =
+ LirCode.builder(getReference(), isD8R8Synthesized(), strategy, appView.options())
+ .setMetadata(IRMetadata.unknown());
+ int instructionIndex = 0;
+ for (; instructionIndex < getNumberOfArguments(); instructionIndex++) {
+ DexType argumentType = getArgumentType(instructionIndex);
+ lirBuilder.addArgument(instructionIndex, argumentType.isBooleanType());
+ }
+
+ // Load tag.
+ Value tagValue = new Value(instructionIndex, stringValueType, null);
+ strategy.defineValue(tagValue, tagValue.getNumber());
+ lirBuilder.addConstString(tag);
+ instructionIndex++;
+
+ // Get logger.
+ Value loggerValue = new Value(instructionIndex, loggerValueType, null);
+ strategy.defineValue(loggerValue, loggerValue.getNumber());
+ lirBuilder.addInvokeStatic(getLogger, ImmutableList.of(tagValue), false);
+ instructionIndex++;
+
+ // Load message.
+ Value messageValue = new Value(instructionIndex, stringValueType, null);
+ strategy.defineValue(messageValue, messageValue.getNumber());
+ lirBuilder.addConstString(message);
+ instructionIndex++;
+
+ // Call logger.
+ lirBuilder.addInvokeVirtual(severe, ImmutableList.of(loggerValue, messageValue));
+ instructionIndex++;
+
+ // Instantiate exception.
+ Value exceptionValue = new Value(instructionIndex, exceptionValueType, null);
+ strategy.defineValue(exceptionValue, exceptionValue.getNumber());
+ lirBuilder
+ .addNewInstance(exceptionType)
+ .addInvokeDirect(
+ exceptionInitMethod, ImmutableList.of(exceptionValue, messageValue), false);
+ instructionIndex += 2;
+
+ // Throw exception.
+ lirBuilder.addThrow(exceptionValue);
+ instructionIndex++;
+
+ return lirBuilder.build();
}
private CfCode toCfCodeThatLogsError(DexItemFactory itemFactory) {
@@ -1292,6 +1310,11 @@
optimizationInfo = info;
}
+ public void unsetOptimizationInfo() {
+ checkIfObsolete();
+ optimizationInfo = DefaultMethodOptimizationInfo.getInstance();
+ }
+
public void copyMetadata(AppView<?> appView, DexEncodedMethod from) {
checkIfObsolete();
if (from.hasClassFileVersion()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index ac780e9..3250adb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -60,4 +60,31 @@
// Always just return the object type since this is safe for all api versions.
return factory.objectType;
}
+
+ public static boolean isTypeAccessibleInMethodContext(
+ AppView<?> appView, DexType type, ProgramMethod context) {
+ if (type.isPrimitiveType()) {
+ return true;
+ }
+ if (type.isIdenticalTo(context.getHolderType())
+ || (context.getHolder().hasSuperType()
+ && type.isIdenticalTo(context.getHolder().getSuperType()))
+ || context.getHolder().getInterfaces().contains(type)) {
+ // In principle we don't know if the supertypes are guaranteed to be accessible in the current
+ // context. However, if they aren't, the current class will never be successfully loaded
+ // anyway.
+ return true;
+ }
+ DexClass clazz = appView.definitionFor(type, context);
+ if (clazz == null) {
+ return false;
+ }
+ if (clazz.isLibraryClass()) {
+ return AndroidApiLevelUtils.isApiSafeForReference(clazz.asLibraryClass(), appView);
+ }
+ if (appView.hasClassHierarchy()) {
+ return AccessControl.isClassAccessible(clazz, context, appView.withClassHierarchy()).isTrue();
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 6b2bad4..29cd5a4 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -9,7 +9,9 @@
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -102,4 +104,17 @@
assert infos.values().size() == SetUtils.newIdentityHashSet(infos.values()).size();
return true;
}
+
+ public FieldAccessInfoCollectionImpl withoutPrunedItems(PrunedItems prunedItems) {
+ Iterator<Entry<DexField, FieldAccessInfoImpl>> iterator = infos.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<DexField, FieldAccessInfoImpl> entry = iterator.next();
+ if (prunedItems.isRemoved(entry.getKey())) {
+ iterator.remove();
+ } else {
+ entry.setValue(entry.getValue().withoutPrunedItems(prunedItems));
+ }
+ }
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 0dae257..39aa866 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -406,4 +406,10 @@
merged.writesWithContexts = writesWithContexts.join(impl.writesWithContexts);
return merged;
}
+
+ public FieldAccessInfoImpl withoutPrunedItems(PrunedItems prunedItems) {
+ readsWithContexts = readsWithContexts.withoutPrunedItems(prunedItems);
+ writesWithContexts = writesWithContexts.withoutPrunedItems(prunedItems);
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index d519324..916a8e2 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -38,7 +38,7 @@
appView.appInfo().hasLiveness()
? appView.appInfo().withLiveness()::wasPruned
: alwaysFalse(),
- appView.graphLens()::lookupType,
+ type -> appView.graphLens().lookupType(type, appView.getGenericSignaturesLens()),
context,
hasGenericTypeVariables);
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 3b8681b..b541bfc 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -17,7 +17,9 @@
import com.android.tools.r8.utils.collections.ThrowingMap;
import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
@@ -178,18 +180,18 @@
}
}
- public MethodAccessInfoCollection withoutPrunedItems(PrunedItems prunedItems) {
+ public MethodAccessInfoCollection withoutPrunedContexts(PrunedItems prunedItems) {
if (!fullyDestroyed) {
- pruneItems(prunedItems, directInvokes);
- pruneItems(prunedItems, interfaceInvokes);
- pruneItems(prunedItems, staticInvokes);
- pruneItems(prunedItems, superInvokes);
- pruneItems(prunedItems, virtualInvokes);
+ pruneContexts(prunedItems, directInvokes);
+ pruneContexts(prunedItems, interfaceInvokes);
+ pruneContexts(prunedItems, staticInvokes);
+ pruneContexts(prunedItems, superInvokes);
+ pruneContexts(prunedItems, virtualInvokes);
}
return this;
}
- private static void pruneItems(
+ private static void pruneContexts(
PrunedItems prunedItems, Map<DexMethod, ProgramMethodSet> invokes) {
if (isThrowingMap(invokes)) {
return;
@@ -212,6 +214,45 @@
});
}
+ public MethodAccessInfoCollection withoutPrunedItems(PrunedItems prunedItems) {
+ if (!fullyDestroyed) {
+ pruneItems(prunedItems, directInvokes);
+ pruneItems(prunedItems, interfaceInvokes);
+ pruneItems(prunedItems, staticInvokes);
+ pruneItems(prunedItems, superInvokes);
+ pruneItems(prunedItems, virtualInvokes);
+ }
+ return this;
+ }
+
+ private static void pruneItems(
+ PrunedItems prunedItems, Map<DexMethod, ProgramMethodSet> invokes) {
+ if (isThrowingMap(invokes)) {
+ return;
+ }
+ Iterator<Entry<DexMethod, ProgramMethodSet>> iterator = invokes.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<DexMethod, ProgramMethodSet> entry = iterator.next();
+ if (prunedItems.isRemoved(entry.getKey())) {
+ iterator.remove();
+ } else {
+ ProgramMethodSet contexts = entry.getValue();
+ contexts.removeIf(
+ context -> {
+ if (prunedItems.isRemoved(context.getReference())) {
+ return true;
+ }
+ assert prunedItems.getPrunedApp().definitionFor(context.getReference()) != null
+ : "Expected method to be present: " + context.getReference().toSourceString();
+ return false;
+ });
+ if (contexts.isEmpty()) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
public boolean verify(AppView<AppInfoWithLiveness> appView) {
assert verifyNoNonResolving(appView);
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
index 70d1666..b55811b 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
@@ -43,5 +43,5 @@
AppInfo appInfo);
ObjectAllocationInfoCollection rewrittenWithLens(
- DexDefinitionSupplier definitions, GraphLens lens, Timing timing);
+ DexDefinitionSupplier definitions, GraphLens lens, GraphLens appliedLens, Timing timing);
}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 6050c9d..2ffe096 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.shaking.GraphReporter;
import com.android.tools.r8.shaking.InstantiationReason;
@@ -144,14 +145,17 @@
@Override
public ObjectAllocationInfoCollectionImpl rewrittenWithLens(
- DexDefinitionSupplier definitions, GraphLens lens, Timing timing) {
+ DexDefinitionSupplier definitions, GraphLens lens, GraphLens appliedLens, Timing timing) {
return timing.time(
- "Rewrite ObjectAllocationInfoCollectionImpl", () -> rewrittenWithLens(definitions, lens));
+ "Rewrite ObjectAllocationInfoCollectionImpl",
+ () -> rewrittenWithLens(definitions, lens, appliedLens));
}
private ObjectAllocationInfoCollectionImpl rewrittenWithLens(
- DexDefinitionSupplier definitions, GraphLens lens) {
- return builder(true, null).rewrittenWithLens(this, definitions, lens).build(definitions);
+ DexDefinitionSupplier definitions, GraphLens lens, GraphLens appliedLens) {
+ return builder(true, null)
+ .rewrittenWithLens(this, definitions, lens, appliedLens)
+ .build(definitions);
}
public ObjectAllocationInfoCollectionImpl withoutPrunedItems(PrunedItems prunedItems) {
@@ -474,11 +478,12 @@
Builder rewrittenWithLens(
ObjectAllocationInfoCollectionImpl objectAllocationInfos,
DexDefinitionSupplier definitions,
- GraphLens lens) {
+ GraphLens lens,
+ GraphLens appliedLens) {
instantiatedHierarchy = null;
objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
clazz -> {
- DexType type = lens.lookupType(clazz.type);
+ DexType type = lens.lookupType(clazz.type, appliedLens);
if (type.isPrimitiveType()) {
return;
}
@@ -488,7 +493,7 @@
});
objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
(clazz, allocationSitesForClass) -> {
- DexType type = lens.lookupType(clazz.type);
+ DexType type = lens.lookupType(clazz.type, appliedLens);
if (type.isPrimitiveType()) {
return;
}
@@ -507,7 +512,7 @@
});
for (DexProgramClass abstractType :
objectAllocationInfos.interfacesWithUnknownSubtypeHierarchy) {
- DexType type = lens.lookupType(abstractType.type);
+ DexType type = lens.lookupType(abstractType.type, appliedLens);
if (type.isPrimitiveType()) {
assert false;
continue;
@@ -517,15 +522,19 @@
assert !interfacesWithUnknownSubtypeHierarchy.contains(rewrittenClass);
interfacesWithUnknownSubtypeHierarchy.add(rewrittenClass);
}
+ LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(definitions, lens, appliedLens);
objectAllocationInfos.instantiatedLambdas.forEach(
(iface, lambdas) -> {
- DexType type = lens.lookupType(iface);
+ DexType type = lens.lookupType(iface, appliedLens);
if (type.isPrimitiveType()) {
assert false;
return;
}
- // TODO(b/150277553): Rewrite lambda descriptor.
- instantiatedLambdas.computeIfAbsent(type, ignoreKey(ArrayList::new)).addAll(lambdas);
+ List<LambdaDescriptor> newLambdas =
+ instantiatedLambdas.computeIfAbsent(type, ignoreKey(ArrayList::new));
+ for (LambdaDescriptor lambda : lambdas) {
+ newLambdas.add(lambda.rewrittenWithLens(lens, appliedLens, rewriter));
+ }
});
return this;
}
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 9b4fa96..a86dbc8 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -204,18 +204,16 @@
return appView.options().debug || getOrComputeReachabilitySensitive(appView);
}
- @SuppressWarnings("ReferenceEquality")
public ProgramMethod rewrittenWithLens(
GraphLens lens, GraphLens appliedLens, DexDefinitionSupplier definitions) {
DexMethod newMethod = lens.getRenamedMethodSignature(getReference(), appliedLens);
- if (newMethod == getReference() && !getDefinition().isObsolete()) {
+ if (newMethod.isIdenticalTo(getReference()) && !getDefinition().isObsolete()) {
assert verifyIsConsistentWithLookup(definitions);
return this;
}
return asProgramMethodOrNull(definitions.definitionFor(newMethod));
}
- @SuppressWarnings("ReferenceEquality")
private boolean verifyIsConsistentWithLookup(DexDefinitionSupplier definitions) {
DexClassAndMethod lookupMethod = definitions.definitionFor(getReference());
assert getDefinition() == lookupMethod.getDefinition();
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
index d0f23ae..96948b9 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -26,10 +26,7 @@
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingLens;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.CollectionUtils;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
@@ -43,7 +40,6 @@
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.ArrayDeque;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
@@ -472,35 +468,6 @@
return true;
}
- public <T extends DexReference> boolean assertPinnedNotModified(
- AppView<AppInfoWithLiveness> appView) {
- List<DexReference> pinnedItems = new ArrayList<>();
- KeepInfoCollection keepInfo = appView.getKeepInfo();
- InternalOptions options = appView.options();
- keepInfo.forEachPinnedType(pinnedItems::add, options);
- keepInfo.forEachPinnedMethod(pinnedItems::add, options);
- keepInfo.forEachPinnedField(pinnedItems::add, options);
- return assertReferencesNotModified(pinnedItems);
- }
-
- @SuppressWarnings("ReferenceEquality")
- public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
- for (DexReference reference : references) {
- if (reference.isDexField()) {
- DexField field = reference.asDexField();
- assert getRenamedFieldSignature(field) == field;
- } else if (reference.isDexMethod()) {
- DexMethod method = reference.asDexMethod();
- assert getRenamedMethodSignature(method) == method;
- } else {
- assert reference.isDexType();
- DexType type = reference.asDexType();
- assert lookupType(type) == type;
- }
- }
- return true;
- }
-
public Map<DexCallSite, ProgramMethodSet> rewriteCallSites(
Map<DexCallSite, ProgramMethodSet> callSites,
DexDefinitionSupplier definitions,
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
index 987bfe3..e657932 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
@@ -12,8 +12,6 @@
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.utils.ThrowingAction;
import com.google.common.collect.Streams;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
public abstract class NonIdentityGraphLens extends GraphLens {
@@ -22,8 +20,6 @@
private final DexItemFactory dexItemFactory;
private GraphLens previousLens;
- private final Map<DexType, DexType> arrayTypeCache = new ConcurrentHashMap<>();
-
public NonIdentityGraphLens(AppView<?> appView) {
this(appView, appView.graphLens());
}
@@ -104,20 +100,14 @@
}
@Override
- @SuppressWarnings("ReferenceEquality")
public final DexType lookupType(DexType type, GraphLens appliedLens) {
if (type.isClassType()) {
return lookupClassType(type, appliedLens);
}
if (type.isArrayType()) {
- DexType result = arrayTypeCache.get(type);
- if (result == null) {
- DexType baseType = type.toBaseType(dexItemFactory);
- DexType newType = lookupType(baseType, appliedLens);
- result = baseType == newType ? type : type.replaceBaseType(newType, dexItemFactory);
- arrayTypeCache.put(type, result);
- }
- return result;
+ DexType baseType = type.toBaseType(dexItemFactory);
+ DexType newType = lookupType(baseType, appliedLens);
+ return baseType.isIdenticalTo(newType) ? type : type.replaceBaseType(newType, dexItemFactory);
}
assert type.isNullValueType() || type.isPrimitiveType() || type.isVoidType();
return type;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index 38b433a..85794e3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StringSwitch;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
@@ -53,7 +54,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 3570828..d784fa3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -438,6 +438,17 @@
}
}
+ public void removeAllExceptionalSuccessors() {
+ assert hasCatchHandlers();
+ IntList successorsToRemove = new IntArrayList();
+ int numberOfExceptionalSuccessors = numberOfExceptionalSuccessors();
+ for (int i = 0; i < numberOfExceptionalSuccessors; i++) {
+ successorsToRemove.add(i);
+ successors.get(i).getMutablePredecessors().remove(this);
+ }
+ removeSuccessorsByIndex(successorsToRemove);
+ }
+
public void swapSuccessors(BasicBlock a, BasicBlock b) {
assert a != b;
int aIndex = successors.indexOf(a);
@@ -1153,6 +1164,36 @@
return true;
}
+ private boolean isExceptionTrampoline() {
+ boolean ret = instructions.size() == 2 && entry().isMoveException() && exit().isGoto();
+ assert !ret || !hasCatchHandlers() : "Trampoline should not have catch handlers";
+ return ret;
+ }
+
+ /** Returns whether the given blocks are in the same try block. */
+ public boolean hasEquivalentCatchHandlers(BasicBlock other) {
+ if (this == other) {
+ return true;
+ }
+ List<Integer> targets1 = catchHandlers.getAllTargets();
+ List<Integer> targets2 = other.catchHandlers.getAllTargets();
+ int numHandlers = targets1.size();
+ if (numHandlers != targets2.size()) {
+ return false;
+ }
+ // If all catch handlers are trampolines to the same block, then they are from the same try.
+ for (int i = 0; i < numHandlers; ++i) {
+ BasicBlock catchBlock1 = successors.get(targets1.get(i));
+ BasicBlock catchBlock2 = other.successors.get(targets2.get(i));
+ if (!catchBlock1.isExceptionTrampoline()
+ || !catchBlock2.isExceptionTrampoline()
+ || catchBlock1.getUniqueSuccessor() != catchBlock2.getUniqueSuccessor()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public void clearCurrentDefinitions() {
currentDefinitions = null;
for (Phi phi : getPhis()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 1717cb3..7da900e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -5,7 +5,9 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
@@ -110,6 +112,12 @@
return new CatchHandlers<>(newGuards, newTargets);
}
+ public CatchHandlers<T> rewriteWithLens(GraphLens graphLens, GraphLens codeLens) {
+ List<DexType> newGuards =
+ ListUtils.mapOrElse(guards, guard -> graphLens.lookupType(guard, codeLens), null);
+ return newGuards != null ? new CatchHandlers<>(newGuards, targets) : this;
+ }
+
public void forEach(BiConsumer<DexType, T> consumer) {
for (int i = 0; i < size(); ++i) {
consumer.accept(guards.get(i), targets.get(i));
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index af8c834..1bd40d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeUtils;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -86,10 +87,17 @@
ProgramMethod context,
AbstractValueSupplier abstractValueSupplier,
SideEffectAssumption assumption) {
- return !(size().definition != null
- && size().definition.isConstNumber()
- && size().definition.asConstNumber().getRawValue() >= 0
- && size().definition.asConstNumber().getRawValue() < Integer.MAX_VALUE);
+ assert type.isArrayType();
+ return isArrayTypeInaccessible(appView, context) || isArraySizeMaybeNegative();
+ }
+
+ private boolean isArrayTypeInaccessible(AppView<?> appView, ProgramMethod context) {
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ return !DexTypeUtils.isTypeAccessibleInMethodContext(appView, baseType, context);
+ }
+
+ private boolean isArraySizeMaybeNegative() {
+ return sizeIfConst() < 0;
}
@Override
@@ -111,12 +119,7 @@
if (instructionInstanceCanThrow(appView, code.context())) {
return DeadInstructionResult.notDead();
}
- // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
- DexType baseType = type.toBaseType(appView.dexItemFactory());
- if (baseType.isPrimitiveType() || appView.definitionFor(baseType) != null) {
- return DeadInstructionResult.deadIfOutValueIsDead();
- }
- return DeadInstructionResult.notDead();
+ return DeadInstructionResult.deadIfOutValueIsDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ba73a12..365708f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -406,6 +406,14 @@
return uniquePhiUsers = ImmutableSet.copyOf(phiUsers);
}
+ public Set<BasicBlock> uniquePhiUserBlocks() {
+ Set<BasicBlock> ret = Sets.newIdentityHashSet();
+ for (Phi phi : phiUsers) {
+ ret.add(phi.getBlock());
+ }
+ return ret;
+ }
+
public Set<Instruction> debugUsers() {
return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
}
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 9f48b43..c03f772 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
@@ -57,6 +57,7 @@
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
import com.android.tools.r8.ir.optimize.PhiOptimizations;
import com.android.tools.r8.ir.optimize.peepholes.BasicBlockMuncher;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
@@ -90,6 +91,7 @@
private CfRegisterAllocator registerAllocator;
private Position currentPosition = Position.none();
+ private Position preamblePosition = null;
private final Int2ReferenceMap<DebugLocalInfo> emittedLocals = new Int2ReferenceOpenHashMap<>();
private Int2ReferenceMap<DebugLocalInfo> pendingLocals = null;
@@ -413,6 +415,7 @@
if (method.getDefinition().getCode().isCfCode()) {
diagnosticPosition = method.getDefinition().getCode().asCfCode().getDiagnosticPosition();
}
+ materializePreamblePosition();
return new CfCode(
method.getHolderType(),
stackHeightTracker.maxHeight,
@@ -424,6 +427,25 @@
bytecodeMetadataBuilder.build());
}
+ private void materializePreamblePosition() {
+ if (!currentPosition.isNone()
+ || preamblePosition == null
+ || !preamblePosition.hasCallerPosition()) {
+ return;
+ }
+ CfLabel existingLabel = ListUtils.first(instructions).asLabel();
+ int instructionIncrement = existingLabel != null ? 1 : 2;
+ List<CfInstruction> newInstructions =
+ new ArrayList<>(instructions.size() + instructionIncrement);
+ CfLabel label = existingLabel != null ? existingLabel : new CfLabel();
+ newInstructions.add(label);
+ newInstructions.add(new CfPosition(label, preamblePosition));
+ for (int i = existingLabel == null ? 0 : 1; i < instructions.size(); i++) {
+ newInstructions.add(instructions.get(i));
+ }
+ instructions = newInstructions;
+ }
+
private static boolean isNopInstruction(Instruction instruction, BasicBlock nextBlock) {
// From DexBuilder
return instruction.isArgument()
@@ -559,6 +581,9 @@
@SuppressWarnings("ReferenceEquality")
private void updatePositionAndLocals(Instruction instruction) {
Position position = instruction.getPosition();
+ if (preamblePosition == null) {
+ preamblePosition = position;
+ }
boolean didLocalsChange = localsChanged();
boolean didPositionChange =
position.isSome()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 6b13747..4fc003f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -67,6 +67,11 @@
}
@Override
+ public boolean isD8MethodProcessor() {
+ return true;
+ }
+
+ @Override
public boolean isProcessedConcurrently(ProgramMethod method) {
// In D8 all methods are considered independently compiled.
return true;
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 9a7b1e0..2dc1a3f 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
@@ -85,7 +85,6 @@
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -129,7 +128,6 @@
protected ServiceLoaderRewriter serviceLoaderRewriter;
protected final EnumUnboxer enumUnboxer;
protected final NumberUnboxer numberUnboxer;
- protected InstanceInitializerOutliner instanceInitializerOutliner;
protected final RemoveVerificationErrorForUnknownReturnedValues
removeVerificationErrorForUnknownReturnedValues;
@@ -218,7 +216,6 @@
this.enumUnboxer = EnumUnboxer.empty();
this.numberUnboxer = NumberUnboxer.empty();
this.assumeInserter = null;
- this.instanceInitializerOutliner = null;
this.removeVerificationErrorForUnknownReturnedValues = null;
return;
}
@@ -230,13 +227,6 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(appView, this)
: null;
- if (appView.options().desugarState.isOn()
- && appView.options().apiModelingOptions().enableOutliningOfMethods
- && appView.options().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
- this.instanceInitializerOutliner = new InstanceInitializerOutliner(appView);
- } else {
- this.instanceInitializerOutliner = null;
- }
removeVerificationErrorForUnknownReturnedValues =
(appView.options().apiModelingOptions().enableLibraryApiModeling
&& appView.options().canHaveVerifyErrorForUnknownUnusedReturnValue())
@@ -652,12 +642,9 @@
timing.end();
previous = printMethod(code, "IR after enum-switch optimization (SSA)", previous);
- if (instanceInitializerOutliner != null) {
- instanceInitializerOutliner.rewriteInstanceInitializers(
- code, context, methodProcessor, methodProcessingContext);
- assert code.verifyTypes(appView);
- previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous);
- }
+ new InstanceInitializerOutliner(appView)
+ .run(code, methodProcessor, methodProcessingContext, timing);
+ previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous);
// Update the IR code if collected call site optimization info has something useful.
// While aggregation of parameter information at call sites would be more precise than static
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
index 013bfde..0e31e93 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriterUtils.java
@@ -118,15 +118,19 @@
.getName();
}
- @SuppressWarnings("ReferenceEquality")
public DexMethodHandle rewriteDexMethodHandle(
DexMethodHandle methodHandle, MethodHandleUse use, ProgramMethod context) {
+ return rewriteDexMethodHandle(methodHandle, use, context.getReference());
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ public DexMethodHandle rewriteDexMethodHandle(
+ DexMethodHandle methodHandle, MethodHandleUse use, DexMethod context) {
if (methodHandle.isMethodHandle()) {
DexMethod invokedMethod = methodHandle.asMethod();
MethodHandleType oldType = methodHandle.type;
MethodLookupResult lensLookup =
- graphLens.lookupMethod(
- invokedMethod, context.getReference(), oldType.toInvokeType(), codeLens);
+ graphLens.lookupMethod(invokedMethod, context, oldType.toInvokeType(), codeLens);
DexMethod rewrittenTarget = lensLookup.getReference();
DexMethod actualTarget;
MethodHandleType newType;
@@ -161,7 +165,7 @@
}
}
if (newType != oldType || actualTarget != invokedMethod || rewrittenTarget != actualTarget) {
- DexClass holder = definitions.definitionFor(actualTarget.holder, context);
+ DexClass holder = definitions.definitionFor(actualTarget.holder);
boolean isInterface = holder != null ? holder.isInterface() : methodHandle.isInterface;
return definitions
.dexItemFactory()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
new file mode 100644
index 0000000..19bb6dc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -0,0 +1,284 @@
+// Copyright (c) 2024, 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;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
+import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.lightir.IR2LirConverter;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirStrategy;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class LirConverter {
+
+ public static void enterLirSupportedPhase(
+ AppView<AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+ throws ExecutionException {
+ assert appView.testing().canUseLir(appView);
+ assert appView.testing().isPreLirPhase();
+ appView.testing().enterLirSupportedPhase();
+ // Convert code objects to LIR.
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ // TODO(b/225838009): Also convert instance initializers to LIR, by adding support for
+ // computing the inlining constraint for LIR and using that in the class mergers, and
+ // class initializers, by updating the concatenation of clinits in horizontal class
+ // merging.
+ clazz.forEachProgramMethodMatching(
+ method ->
+ method.hasCode()
+ && !method.isInitializer()
+ && !appView.isCfByteCodePassThrough(method),
+ method -> {
+ IRCode code = method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
+ LirCode<Integer> lirCode =
+ IR2LirConverter.translate(
+ code,
+ BytecodeMetadataProvider.empty(),
+ LirStrategy.getDefaultStrategy().getEncodingStrategy(),
+ appView.options());
+ // TODO(b/312890994): Setting a custom code lens is only needed until we convert
+ // code objects to LIR before we create the first code object with a custom code
+ // lens (horizontal class merging).
+ GraphLens codeLens = method.getDefinition().getCode().getCodeLens(appView);
+ if (codeLens != appView.codeLens()) {
+ lirCode =
+ new LirCode<>(lirCode) {
+ @Override
+ public GraphLens getCodeLens(AppView<?> appView) {
+ return codeLens;
+ }
+ };
+ }
+ method.setCode(lirCode, appView);
+ });
+ },
+ appView.options().getThreadingModule(),
+ executorService);
+ // Conversion to LIR via IR will allocate type elements.
+ // They are not needed after construction so remove them again.
+ appView.dexItemFactory().clearTypeElementsCache();
+ }
+
+ public static void rewriteLirWithLens(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Timing timing,
+ ExecutorService executorService)
+ throws ExecutionException {
+ assert appView.testing().canUseLir(appView);
+ assert appView.testing().isSupportedLirPhase();
+ assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
+ assert verifyLirOnly(appView);
+
+ GraphLens graphLens = appView.graphLens();
+ assert graphLens.isNonIdentityLens();
+ assert appView.codeLens().isAppliedLens();
+
+ MemberRebindingIdentityLens memberRebindingIdentityLens =
+ graphLens.asNonIdentityLens().find(GraphLens::isMemberRebindingIdentityLens);
+ assert memberRebindingIdentityLens != null;
+ if (graphLens == memberRebindingIdentityLens
+ && memberRebindingIdentityLens.getPrevious().isAppliedLens()) {
+ // Nothing to rewrite.
+ return;
+ }
+
+ timing.begin("LIR->LIR@" + graphLens.getClass().getTypeName());
+ rewriteLirWithUnappliedLens(appView, executorService);
+ timing.end();
+
+ // At this point all code has been mapped according to the graph lens.
+ updateCodeLens(appView);
+ }
+
+ private static void rewriteLirWithUnappliedLens(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+ throws ExecutionException {
+ LensCodeRewriterUtils rewriterUtils = new LensCodeRewriterUtils(appView, true);
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz ->
+ clazz.forEachProgramMethodMatching(
+ m ->
+ m.hasCode()
+ && !m.getCode().isSharedCodeObject()
+ && !appView.isCfByteCodePassThrough(m),
+ m -> rewriteLirMethodWithLens(m, appView, rewriterUtils)),
+ appView.options().getThreadingModule(),
+ executorService);
+
+ // Clear the reference type cache after conversion to reduce memory pressure.
+ appView.dexItemFactory().clearTypeElementsCache();
+ }
+
+ private static void rewriteLirMethodWithLens(
+ ProgramMethod method,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ LensCodeRewriterUtils rewriterUtils) {
+ Code code = method.getDefinition().getCode();
+ if (!code.isLirCode()) {
+ assert false;
+ return;
+ }
+ LirCode<Integer> lirCode = code.asLirCode();
+ LirCode<Integer> rewrittenLirCode =
+ lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
+ if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
+ method.setCode(rewrittenLirCode, appView);
+ }
+ }
+
+ public static void finalizeLirToOutputFormat(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Timing timing,
+ ExecutorService executorService)
+ throws ExecutionException {
+ assert appView.testing().canUseLir(appView);
+ assert appView.testing().isSupportedLirPhase();
+ assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
+ assert verifyLirOnly(appView);
+ appView.testing().exitLirSupportedPhase();
+ LensCodeRewriterUtils rewriterUtils = new LensCodeRewriterUtils(appView, true);
+ DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+ String output = appView.options().isGeneratingClassFiles() ? "CF" : "DEX";
+ timing.begin("LIR->IR->" + output);
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz ->
+ clazz.forEachProgramMethod(
+ m -> finalizeLirMethodToOutputFormat(m, deadCodeRemover, appView, rewriterUtils)),
+ appView.options().getThreadingModule(),
+ executorService);
+ timing.end();
+ // Clear the reference type cache after conversion to reduce memory pressure.
+ appView.dexItemFactory().clearTypeElementsCache();
+ // At this point all code has been mapped according to the graph lens.
+ updateCodeLens(appView);
+ }
+
+ private static void updateCodeLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ final NonIdentityGraphLens lens = appView.graphLens().asNonIdentityLens();
+ if (lens == null) {
+ assert false;
+ return;
+ }
+
+ // If the current graph lens is the member rebinding identity lens then code lens is simply
+ // the previous lens. This is the same structure as the more complicated case below but where
+ // there is no need to rewrite any previous pointers.
+ if (lens.isMemberRebindingIdentityLens()) {
+ appView.setCodeLens(lens.getPrevious());
+ return;
+ }
+
+ // Otherwise search out where the lens pointing to the member rebinding identity lens.
+ NonIdentityGraphLens lensAfterMemberRebindingIdentityLens =
+ lens.find(p -> p.getPrevious().isMemberRebindingIdentityLens());
+ if (lensAfterMemberRebindingIdentityLens == null) {
+ // With the current compiler structure we expect to always find the lens.
+ assert false;
+ appView.setCodeLens(lens);
+ return;
+ }
+
+ GraphLens codeLens = appView.codeLens();
+ MemberRebindingIdentityLens memberRebindingIdentityLens =
+ lensAfterMemberRebindingIdentityLens.getPrevious().asMemberRebindingIdentityLens();
+
+ // We are assuming that the member rebinding identity lens is always installed after the current
+ // applied lens/code lens and also that there should not be a rebinding lens from the compilers
+ // first phase (this subroutine is only used after IR conversion for now).
+ assert memberRebindingIdentityLens
+ == lens.findPrevious(
+ p -> p == memberRebindingIdentityLens || p == codeLens || p.isMemberRebindingLens());
+
+ // Rewrite the graph lens effects from 'lens' and up to the member rebinding identity lens.
+ MemberRebindingIdentityLens rewrittenMemberRebindingLens =
+ memberRebindingIdentityLens.toRewrittenMemberRebindingIdentityLens(
+ appView, lens, memberRebindingIdentityLens, lens);
+
+ // The current previous pointers for the graph lenses are:
+ // lens -> ... -> lensAfterMemberRebindingIdentityLens -> memberRebindingIdentityLens -> g
+ // we rewrite them now to:
+ // rewrittenMemberRebindingLens -> lens -> ... -> lensAfterMemberRebindingIdentityLens -> g
+
+ // The above will construct the new member rebinding lens such that it points to the new
+ // code-lens point already.
+ assert rewrittenMemberRebindingLens.getPrevious() == lens;
+
+ // Update the previous pointer on the new code lens to jump over the old member rebinding
+ // identity lens.
+ lensAfterMemberRebindingIdentityLens.setPrevious(memberRebindingIdentityLens.getPrevious());
+
+ // The applied lens can now be updated and the rewritten member rebinding lens installed as
+ // the current "unapplied lens".
+ appView.setCodeLens(lens);
+ appView.setGraphLens(rewrittenMemberRebindingLens);
+ }
+
+ private static void finalizeLirMethodToOutputFormat(
+ ProgramMethod method,
+ DeadCodeRemover deadCodeRemover,
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ LensCodeRewriterUtils rewriterUtils) {
+ Code code = method.getDefinition().getCode();
+ if (!(code instanceof LirCode)) {
+ return;
+ }
+ Timing onThreadTiming = Timing.empty();
+ LirCode<Integer> lirCode = code.asLirCode();
+ LirCode<Integer> rewrittenLirCode =
+ lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
+ if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
+ method.setCode(rewrittenLirCode, appView);
+ }
+ IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
+ FilledNewArrayRewriter filledNewArrayRewriter = new FilledNewArrayRewriter(appView);
+ boolean changed = filledNewArrayRewriter.run(irCode, onThreadTiming).hasChanged().toBoolean();
+ if (appView.options().isGeneratingDex() && changed) {
+ ConstantCanonicalizer constantCanonicalizer =
+ new ConstantCanonicalizer(appView, method, irCode);
+ constantCanonicalizer.canonicalize();
+ }
+ // Processing is done and no further uses of the meta-data should arise.
+ BytecodeMetadataProvider noMetadata = BytecodeMetadataProvider.empty();
+ // During processing optimization info may cause previously live code to become dead.
+ // E.g., we may now have knowledge that an invoke does not have side effects.
+ // Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization.
+ deadCodeRemover.run(irCode, onThreadTiming);
+ MethodConversionOptions conversionOptions = irCode.getConversionOptions();
+ assert !conversionOptions.isGeneratingLir();
+ IRFinalizer<?> finalizer = conversionOptions.getFinalizer(deadCodeRemover, appView);
+ method.setCode(finalizer.finalizeCode(irCode, noMetadata, onThreadTiming), appView);
+ }
+
+ public static boolean verifyLirOnly(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ for (DexEncodedMethod method : clazz.methods(DexEncodedMethod::hasCode)) {
+ assert method.getCode().isLirCode()
+ || method.getCode().isSharedCodeObject()
+ || appView.isCfByteCodePassThrough(method)
+ || appView.options().skipIR;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index a80055a..829c985 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -17,6 +17,10 @@
return null;
}
+ public boolean isD8MethodProcessor() {
+ return false;
+ }
+
public boolean isPrimaryMethodProcessor() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index e1a4c6f..625a337 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -71,10 +71,6 @@
reportNestDesugarDependencies();
clearNestAttributes();
- if (instanceInitializerOutliner != null) {
- processSimpleSynthesizeMethods(
- instanceInitializerOutliner.getSynthesizedMethods(), executorService);
- }
if (assertionErrorTwoArgsConstructorRewriter != null) {
processSimpleSynthesizeMethods(
assertionErrorTwoArgsConstructorRewriter.getSynthesizedMethods(), executorService);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index e87faba..3dd62c9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -6,28 +6,17 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.passes.FilledNewArrayRewriter;
-import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
-import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.info.MethodResolutionOptimizationInfoAnalysis;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.lightir.LirCode;
-import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
import com.android.tools.r8.optimize.compose.ComposableOptimizationPass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.io.IOException;
@@ -239,139 +228,6 @@
return appView.appInfo().app();
}
- public static void finalizeLirToOutputFormat(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- Timing timing,
- ExecutorService executorService)
- throws ExecutionException {
- appView.testing().exitLirSupportedPhase();
- if (!appView.testing().canUseLir(appView)) {
- return;
- }
- LensCodeRewriterUtils rewriterUtils = new LensCodeRewriterUtils(appView, true);
- DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
- String output = appView.options().isGeneratingClassFiles() ? "CF" : "DEX";
- timing.begin("LIR->IR->" + output);
- ThreadUtils.processItems(
- appView.appInfo().classes(),
- clazz ->
- clazz.forEachProgramMethod(
- m -> finalizeLirMethodToOutputFormat(m, deadCodeRemover, appView, rewriterUtils)),
- appView.options().getThreadingModule(),
- executorService);
- appView
- .getSyntheticItems()
- .getPendingSyntheticClasses()
- .forEach(
- clazz ->
- clazz.forEachProgramMethod(
- m ->
- finalizeLirMethodToOutputFormat(
- m, deadCodeRemover, appView, rewriterUtils)));
- timing.end();
- // Clear the reference type cache after conversion to reduce memory pressure.
- appView.dexItemFactory().clearTypeElementsCache();
- // At this point all code has been mapped according to the graph lens.
- updateCodeLens(appView);
- }
-
- private static void updateCodeLens(AppView<? extends AppInfoWithClassHierarchy> appView) {
- final NonIdentityGraphLens lens = appView.graphLens().asNonIdentityLens();
- if (lens == null) {
- assert false;
- return;
- }
-
- // If the current graph lens is the member rebinding identity lens then code lens is simply
- // the previous lens. This is the same structure as the more complicated case below but where
- // there is no need to rewrite any previous pointers.
- if (lens.isMemberRebindingIdentityLens()) {
- appView.setCodeLens(lens.getPrevious());
- return;
- }
-
- // Otherwise search out where the lens pointing to the member rebinding identity lens.
- NonIdentityGraphLens lensAfterMemberRebindingIdentityLens =
- lens.find(p -> p.getPrevious().isMemberRebindingIdentityLens());
- if (lensAfterMemberRebindingIdentityLens == null) {
- // With the current compiler structure we expect to always find the lens.
- assert false;
- appView.setCodeLens(lens);
- return;
- }
-
- GraphLens codeLens = appView.codeLens();
- MemberRebindingIdentityLens memberRebindingIdentityLens =
- lensAfterMemberRebindingIdentityLens.getPrevious().asMemberRebindingIdentityLens();
-
- // We are assuming that the member rebinding identity lens is always installed after the current
- // applied lens/code lens and also that there should not be a rebinding lens from the compilers
- // first phase (this subroutine is only used after IR conversion for now).
- assert memberRebindingIdentityLens
- == lens.findPrevious(
- p -> p == memberRebindingIdentityLens || p == codeLens || p.isMemberRebindingLens());
-
- // Rewrite the graph lens effects from 'lens' and up to the member rebinding identity lens.
- MemberRebindingIdentityLens rewrittenMemberRebindingLens =
- memberRebindingIdentityLens.toRewrittenMemberRebindingIdentityLens(
- appView, lens, memberRebindingIdentityLens, lens);
-
- // The current previous pointers for the graph lenses are:
- // lens -> ... -> lensAfterMemberRebindingIdentityLens -> memberRebindingIdentityLens -> g
- // we rewrite them now to:
- // rewrittenMemberRebindingLens -> lens -> ... -> lensAfterMemberRebindingIdentityLens -> g
-
- // The above will construct the new member rebinding lens such that it points to the new
- // code-lens point already.
- assert rewrittenMemberRebindingLens.getPrevious() == lens;
-
- // Update the previous pointer on the new code lens to jump over the old member rebinding
- // identity lens.
- lensAfterMemberRebindingIdentityLens.setPrevious(memberRebindingIdentityLens.getPrevious());
-
- // The applied lens can now be updated and the rewritten member rebinding lens installed as
- // the current "unapplied lens".
- appView.setCodeLens(lens);
- appView.setGraphLens(rewrittenMemberRebindingLens);
- }
-
- @SuppressWarnings("ReferenceEquality")
- private static void finalizeLirMethodToOutputFormat(
- ProgramMethod method,
- DeadCodeRemover deadCodeRemover,
- AppView<?> appView,
- LensCodeRewriterUtils rewriterUtils) {
- Code code = method.getDefinition().getCode();
- if (!(code instanceof LirCode)) {
- return;
- }
- Timing onThreadTiming = Timing.empty();
- LirCode<Integer> lirCode = code.asLirCode();
- LirCode<Integer> rewrittenLirCode =
- lirCode.rewriteWithSimpleLens(method, appView, rewriterUtils);
- if (lirCode != rewrittenLirCode) {
- method.setCode(rewrittenLirCode, appView);
- }
- IRCode irCode = method.buildIR(appView, MethodConversionOptions.forPostLirPhase(appView));
- FilledNewArrayRewriter filledNewArrayRewriter = new FilledNewArrayRewriter(appView);
- boolean changed = filledNewArrayRewriter.run(irCode, onThreadTiming).hasChanged().toBoolean();
- if (appView.options().isGeneratingDex() && changed) {
- ConstantCanonicalizer constantCanonicalizer =
- new ConstantCanonicalizer(appView, method, irCode);
- constantCanonicalizer.canonicalize();
- }
- // Processing is done and no further uses of the meta-data should arise.
- BytecodeMetadataProvider noMetadata = BytecodeMetadataProvider.empty();
- // During processing optimization info may cause previously live code to become dead.
- // E.g., we may now have knowledge that an invoke does not have side effects.
- // Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization.
- deadCodeRemover.run(irCode, onThreadTiming);
- MethodConversionOptions conversionOptions = irCode.getConversionOptions();
- assert !conversionOptions.isGeneratingLir();
- IRFinalizer<?> finalizer = conversionOptions.getFinalizer(deadCodeRemover, appView);
- method.setCode(finalizer.finalizeCode(irCode, noMetadata, onThreadTiming), appView);
- }
-
private void clearDexMethodCompilationState() {
appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
}
@@ -430,10 +286,6 @@
if (inliner != null) {
inliner.onLastWaveDone(postMethodProcessorBuilder, executorService, timing);
}
- if (instanceInitializerOutliner != null) {
- instanceInitializerOutliner.onLastWaveDone(postMethodProcessorBuilder);
- instanceInitializerOutliner = null;
- }
if (serviceLoaderRewriter != null) {
serviceLoaderRewriter.onLastWaveDone(postMethodProcessorBuilder);
serviceLoaderRewriter = null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
index df8d38a..41379ad 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -7,24 +7,28 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeUtils;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilled;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.DominatorChecker;
import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
-import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.ValueUtils;
+import com.android.tools.r8.utils.ValueUtils.ArrayValues;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -79,8 +83,11 @@
*/
public class ArrayConstructionSimplifier extends CodeRewriterPass<AppInfo> {
+ private final RewriteArrayOptions rewriteArrayOptions;
+
public ArrayConstructionSimplifier(AppView<?> appView) {
super(appView);
+ rewriteArrayOptions = options.rewriteArrayOptions();
}
@Override
@@ -90,228 +97,271 @@
@Override
protected CodeRewriterResult rewriteCode(IRCode code) {
- boolean hasChanged = false;
- WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
- while (worklist.hasNext()) {
- BasicBlock block = worklist.next();
- hasChanged |= simplifyArrayConstructionBlock(block, worklist, code, appView.options());
+ ArrayList<ArrayValues> candidates = findOptimizableArrays(code);
+
+ if (candidates.isEmpty()) {
+ return CodeRewriterResult.NO_CHANGE;
}
- return CodeRewriterResult.hasChanged(hasChanged);
+ applyChanges(code, candidates);
+ return CodeRewriterResult.HAS_CHANGED;
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return code.metadata().mayHaveNewArrayEmpty();
}
- private boolean simplifyArrayConstructionBlock(
- BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
- boolean hasChanged = false;
- RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- FilledArrayCandidate candidate = computeFilledArrayCandidate(it.next(), rewriteOptions);
- if (candidate == null) {
- continue;
- }
- FilledArrayConversionInfo info =
- computeConversionInfo(
- code, candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
- if (info == null) {
- continue;
- }
-
- Instruction instructionAfterCandidate = it.peekNext();
- NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
- DexType arrayType = newArrayEmpty.type;
- int size = candidate.size;
- Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
- assert newArrayEmpty.getLocalInfo() == null;
- Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
- Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
- NewArrayFilled invoke =
- new NewArrayFilled(arrayType, invokeValue, Arrays.asList(info.values));
- invoke.setPosition(lastArrayPut.getPosition());
- for (Value value : newArrayEmpty.inValues()) {
- value.removeUser(newArrayEmpty);
- }
- newArrayEmpty.outValue().replaceUsers(invokeValue);
- instructionsToRemove.add(newArrayEmpty);
-
- boolean originalAllocationPointHasHandlers = block.hasCatchHandlers();
- boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
-
- if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
- info.lastArrayPutIterator.add(invoke);
- } else {
- BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
- if (originalAllocationPointHasHandlers) {
- if (!insertionBlock.isTrivialGoto()) {
- BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
- assert insertionBlock.isTrivialGoto();
- worklist.addIfNotSeen(blockAfterInsertion);
- }
- insertionBlock.moveCatchHandlers(block);
- } else {
- worklist.addIfNotSeen(insertionBlock);
- }
- insertionBlock.listIterator(code).add(invoke);
- }
-
- instructionsToRemove.addAll(info.arrayPutsToRemove);
- Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
- for (Instruction instruction : instructionsToRemove) {
- BasicBlock ownerBlock = instruction.getBlock();
- // If owner block is null, then the instruction has been removed already. We can't rely on
- // just having the block pointer nulled, so the visited blocks guards reprocessing.
- if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
- InstructionListIterator removeIt = ownerBlock.listIterator(code);
- while (removeIt.hasNext()) {
- if (instructionsToRemove.contains(removeIt.next())) {
- removeIt.removeOrReplaceByDebugLocalRead();
- }
- }
+ private ArrayList<ArrayValues> findOptimizableArrays(IRCode code) {
+ ArrayList<ArrayValues> candidates = new ArrayList<>();
+ for (Instruction instruction : code.instructions()) {
+ NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+ if (newArrayEmpty != null) {
+ ArrayValues arrayValues = analyzeCandidate(newArrayEmpty, code);
+ if (arrayValues != null) {
+ candidates.add(arrayValues);
}
}
-
- // The above has invalidated the block iterator so reset it and continue.
- it = block.listIterator(code, instructionAfterCandidate);
- hasChanged = true;
}
- if (hasChanged) {
- code.removeRedundantBlocks();
- }
-
- return hasChanged;
+ return candidates;
}
- private static class FilledArrayConversionInfo {
-
- Value[] values;
- List<ArrayPut> arrayPutsToRemove;
- LinearFlowInstructionListIterator lastArrayPutIterator;
-
- public FilledArrayConversionInfo(int size) {
- values = new Value[size];
- arrayPutsToRemove = new ArrayList<>(size);
+ private ArrayValues analyzeCandidate(NewArrayEmpty newArrayEmpty, IRCode code) {
+ if (newArrayEmpty.getLocalInfo() != null) {
+ return null;
}
+ if (!rewriteArrayOptions.isPotentialSize(newArrayEmpty.sizeIfConst())) {
+ return null;
+ }
+
+ ArrayValues arrayValues = ValueUtils.computeInitialArrayValues(newArrayEmpty);
+ // Holes (default-initialized entries) could be supported, but they are rare and would
+ // complicate the logic.
+ if (arrayValues == null || arrayValues.containsHoles()) {
+ return null;
+ }
+
+ // See if all instructions are in the same try/catch.
+ ArrayPut lastArrayPut = ArrayUtils.last(arrayValues.getArrayPutsByIndex());
+ if (!newArrayEmpty.getBlock().hasEquivalentCatchHandlers(lastArrayPut.getBlock())) {
+ // Possible improvements:
+ // 1) Ignore catch handlers that do not catch OutOfMemoryError / NoClassDefFoundError.
+ // 2) Use the catch handlers from the new-array-empty if all exception blocks exit without
+ // side effects (e.g. no method calls & no monitor instructions).
+ return null;
+ }
+
+ if (!checkTypesAreCompatible(arrayValues, code)) {
+ return null;
+ }
+ if (!checkDominance(arrayValues)) {
+ return null;
+ }
+ return arrayValues;
}
- @SuppressWarnings("ReferenceEquality")
- private FilledArrayConversionInfo computeConversionInfo(
- IRCode code, FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
- NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
- assert it.peekPrevious() == newArrayEmpty;
- Value arrayValue = newArrayEmpty.outValue();
- int size = candidate.size;
-
+ private boolean checkTypesAreCompatible(ArrayValues arrayValues, IRCode code) {
// aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
// if types require a cast.
// TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
// if aput-object throws a ClassCastException if given an object that does not implement the
// desired interface, then we could add check-cast instructions for arguments we're not sure
// about.
+ NewArrayEmpty newArrayEmpty = arrayValues.getDefinition().asNewArrayEmpty();
DexType elementType = newArrayEmpty.type.toArrayElementType(dexItemFactory);
boolean needsTypeCheck =
- !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+ !elementType.isPrimitiveType() && elementType.isNotIdenticalTo(dexItemFactory.objectType);
+ if (!needsTypeCheck) {
+ return true;
+ }
- FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
- Value[] values = info.values;
- int remaining = size;
- Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
- while (it.hasNext()) {
- Instruction instruction = it.next();
- BasicBlock block = instruction.getBlock();
- // If we encounter an instruction that can throw an exception we need to bail out of the
- // optimization so that we do not transform half-initialized arrays into fully initialized
- // arrays on exceptional edges. If the block has no handlers it is not observable so
- // we perform the rewriting.
- if (block.hasCatchHandlers()
- && instruction.instructionInstanceCanThrow(appView, code.context())) {
- return null;
- }
- if (!users.contains(instruction)) {
- // If any instruction can transfer control between the new-array and the last array put
- // then it is not safe to move the new array to the point of the last put.
- if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
- return null;
- }
+ // Not safe to move allocation if NoClassDefError is possible.
+ // TODO(b/246971330): Make this work for D8 where it ~always returns false by checking that
+ // all instructions between new-array-empty and the last array-put report
+ // !instruction.instructionMayHaveSideEffects(). Alternatively, we could replace the
+ // new-array-empty with a const-class instruction in this case.
+ if (!DexTypeUtils.isTypeAccessibleInMethodContext(
+ appView, elementType.toBaseType(dexItemFactory), code.context())) {
+ return false;
+ }
+
+ for (ArrayPut arrayPut : arrayValues.getArrayPutsByIndex()) {
+ Value value = arrayPut.value();
+ if (value.isAlwaysNull(appView)) {
continue;
}
- ArrayPut arrayPut = instruction.asArrayPut();
- // If the initialization sequence is broken by another use we cannot use a fill-array-data
- // instruction.
+ DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+ if (elementType.isArrayType()) {
+ if (elementType.isNotIdenticalTo(valueDexType)) {
+ return false;
+ }
+ } else if (valueDexType.isArrayType()) {
+ // isSubtype asserts for this case.
+ return false;
+ } else if (valueDexType.isNullValueType()) {
+ // Assume instructions can cause value.isAlwaysNull() == false while the DexType is
+ // null.
+ // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+ // that hits this case.
+ } else {
+ // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+ // library types (which this helper does not do).
+ if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static boolean checkDominance(ArrayValues arrayValues) {
+ Value arrayValue = arrayValues.getArrayValue();
+
+ Set<BasicBlock> usageBlocks = Sets.newIdentityHashSet();
+ Set<Instruction> uniqueUsers = arrayValue.uniqueUsers();
+ for (Instruction user : uniqueUsers) {
+ ArrayPut arrayPut = user.asArrayPut();
if (arrayPut == null || arrayPut.array() != arrayValue) {
- return null;
+ usageBlocks.add(user.getBlock());
}
- int index = arrayPut.indexIfConstAndInBounds(values.length);
- if (index < 0 || values[index] != null) {
- return null;
+ }
+
+ // Ensure all blocks for users of the array are dominated by the last array-put's block.
+ ArrayPut lastArrayPut = ArrayUtils.last(arrayValues.getArrayPutsByIndex());
+ BasicBlock lastArrayPutBlock = lastArrayPut.getBlock();
+ BasicBlock subgraphEntryBlock = arrayValue.definition.getBlock();
+ for (BasicBlock usageBlock : usageBlocks) {
+ if (!DominatorChecker.check(subgraphEntryBlock, usageBlock, lastArrayPutBlock)) {
+ return false;
}
- if (arrayPut.instructionInstanceCanThrow(appView, code.context())) {
- return null;
+ }
+
+ // Ensure all array users in the same block appear after the last array-put
+ for (Instruction inst : lastArrayPutBlock.getInstructions()) {
+ if (inst == lastArrayPut) {
+ break;
}
- Value value = arrayPut.value();
- if (needsTypeCheck && !value.isAlwaysNull(appView)) {
- DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
- if (elementType.isArrayType()) {
- if (elementType != valueDexType) {
- return null;
- }
- } else if (valueDexType.isArrayType()) {
- // isSubtype asserts for this case.
- return null;
- } else if (valueDexType.isNullValueType()) {
- // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
- // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
- // that hits this case.
- } else {
- // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
- // library types (which this helper does not do).
- if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
- return null;
+ if (uniqueUsers.contains(inst)) {
+ ArrayPut arrayPut = inst.asArrayPut();
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ return false;
+ }
+ }
+ }
+
+ // It will not be the case that the newArrayEmpty dominates the phi user (or else it would
+ // just be a normal user). It is safe to optimize if all paths from the new-array-empty to the
+ // phi user include the last array-put (where the filled-new-array will end up).
+ if (anyPhiUsersReachableWhenOptimized(arrayValues)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determines if there are any paths from the new-array-empty to any of its phi users that do not
+ * go through the last array-put, and that do not go through exceptional edges for new-array-empty
+ * / array-puts that will be removed.
+ */
+ private static boolean anyPhiUsersReachableWhenOptimized(ArrayValues arrayValues) {
+ Value arrayValue = arrayValues.getArrayValue();
+ if (!arrayValue.hasPhiUsers()) {
+ return false;
+ }
+ Set<BasicBlock> phiUserBlocks = arrayValue.uniquePhiUserBlocks();
+ WorkList<BasicBlock> workList = WorkList.newIdentityWorkList();
+ // Mark the last array-put as seen in order to find paths that do not contain it.
+ workList.markAsSeen(ArrayUtils.last(arrayValues.getArrayPutsByIndex()).getBlock());
+ // Start with normal successors since if optimized, the new-array-empty block will have no
+ // throwing instructions.
+ workList.addIfNotSeen(arrayValue.definition.getBlock().getNormalSuccessors());
+ while (workList.hasNext()) {
+ BasicBlock current = workList.removeLast();
+ if (phiUserBlocks.contains(current)) {
+ return true;
+ }
+ if (current.hasCatchHandlers()) {
+ Instruction throwingInstruction = current.exceptionalExit();
+ if (throwingInstruction != null) {
+ ArrayPut arrayPut = throwingInstruction.asArrayPut();
+ // Ignore exceptional edges that will be remove if optimized.
+ if (arrayPut != null && arrayPut.array() == arrayValue) {
+ workList.addIfNotSeen(current.getNormalSuccessors());
+ continue;
}
}
}
- info.arrayPutsToRemove.add(arrayPut);
- values[index] = value;
- --remaining;
- if (remaining == 0) {
- info.lastArrayPutIterator = it;
- return info;
+ workList.addIfNotSeen(current.getSuccessors());
+ }
+ return false;
+ }
+
+ private void applyChanges(IRCode code, List<ArrayValues> candidates) {
+ Set<BasicBlock> relevantBlocks = Sets.newIdentityHashSet();
+ // All keys instructionsToChange are removed, and also maps lastArrayPut -> newArrayFilled.
+ Map<Instruction, Instruction> instructionsToChange = new IdentityHashMap<>();
+ boolean needToRemoveUnreachableBlocks = false;
+
+ for (ArrayValues arrayValues : candidates) {
+ NewArrayEmpty newArrayEmpty = arrayValues.getDefinition().asNewArrayEmpty();
+ instructionsToChange.put(newArrayEmpty, newArrayEmpty);
+ BasicBlock allocationBlock = newArrayEmpty.getBlock();
+ relevantBlocks.add(allocationBlock);
+
+ ArrayPut[] arrayPutsByIndex = arrayValues.getArrayPutsByIndex();
+ int lastArrayPutIndex = arrayPutsByIndex.length - 1;
+ for (int i = 0; i < lastArrayPutIndex; ++i) {
+ ArrayPut arrayPut = arrayPutsByIndex[i];
+ instructionsToChange.put(arrayPut, arrayPut);
+ relevantBlocks.add(arrayPut.getBlock());
+ }
+ ArrayPut lastArrayPut = arrayPutsByIndex[lastArrayPutIndex];
+ BasicBlock lastArrayPutBlock = lastArrayPut.getBlock();
+ relevantBlocks.add(lastArrayPutBlock);
+
+ // newArrayEmpty's outValue must be cleared before trying to remove newArrayEmpty. Rather than
+ // store the outValue for later, create and store newArrayFilled.
+ Value arrayValue = newArrayEmpty.clearOutValue();
+ NewArrayFilled newArrayFilled =
+ new NewArrayFilled(newArrayEmpty.type, arrayValue, arrayValues.getElementValues());
+ newArrayFilled.setPosition(lastArrayPut.getPosition());
+ instructionsToChange.put(lastArrayPut, newArrayFilled);
+
+ if (arrayValue.hasPhiUsers() && allocationBlock.hasCatchHandlers()) {
+ // When phi users exist, the phis belong to the exceptional successors of the allocation
+ // block. In order to preserve them, move them to the new allocation block.
+ // This is safe because we've already checked hasEquivalentCatchHandlers().
+ lastArrayPutBlock.removeAllExceptionalSuccessors();
+ lastArrayPutBlock.moveCatchHandlers(allocationBlock);
+ needToRemoveUnreachableBlocks = true;
}
}
- return null;
- }
- private static class FilledArrayCandidate {
+ for (BasicBlock block : relevantBlocks) {
+ boolean hasCatchHandlers = block.hasCatchHandlers();
+ InstructionListIterator it = block.listIterator(code);
+ while (it.hasNext()) {
+ Instruction possiblyNewArray = instructionsToChange.get(it.next());
+ if (possiblyNewArray != null) {
+ if (possiblyNewArray.isNewArrayFilled()) {
+ // Change the last array-put to the new-array-filled.
+ it.replaceCurrentInstruction(possiblyNewArray);
+ } else {
+ it.removeOrReplaceByDebugLocalRead();
+ if (hasCatchHandlers) {
+ // Removing these catch handlers shrinks their ranges to be only that where the
+ // allocation occurs.
+ needToRemoveUnreachableBlocks = true;
+ assert !block.canThrow();
+ block.removeAllExceptionalSuccessors();
+ }
+ }
+ }
+ }
+ }
- final NewArrayEmpty newArrayEmpty;
- final int size;
-
- public FilledArrayCandidate(NewArrayEmpty newArrayEmpty, int size) {
- assert size > 0;
- this.newArrayEmpty = newArrayEmpty;
- this.size = size;
+ if (needToRemoveUnreachableBlocks) {
+ code.removeUnreachableBlocks();
}
- }
-
- private FilledArrayCandidate computeFilledArrayCandidate(
- Instruction instruction, RewriteArrayOptions options) {
- NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
- if (newArrayEmpty == null) {
- return null;
- }
- if (instruction.getLocalInfo() != null) {
- return null;
- }
- if (!newArrayEmpty.size().isConstant()) {
- return null;
- }
- int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
- if (!options.isPotentialSize(size)) {
- return null;
- }
- return new FilledArrayCandidate(newArrayEmpty, size);
+ code.removeRedundantBlocks();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
index e8672b3..160335c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BinopRewriter.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.code.Ushr;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.Xor;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableMap;
@@ -244,7 +245,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return options.testing.enableBinopOptimization
&& !isDebugMode(code.context())
&& code.metadata().mayHaveArithmeticOrLogicalBinop();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
index c663705..978a99f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/BranchSimplifier.java
@@ -78,7 +78,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return code.metadata().mayHaveIf() || code.metadata().mayHaveSwitch();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
index 73afe6a..98a92e8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
@@ -50,7 +50,7 @@
IRCode code,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
- if (shouldRewriteCode(code)) {
+ if (shouldRewriteCode(code, methodProcessor)) {
assert verifyConsistentCode(code, isAcceptingSSA(), "before");
CodeRewriterResult result = rewriteCode(code, methodProcessor, methodProcessingContext);
assert result.hasChanged().isFalse() || verifyConsistentCode(code, isProducingSSA(), "after");
@@ -100,5 +100,5 @@
return rewriteCode(code);
}
- protected abstract boolean shouldRewriteCode(IRCode code);
+ protected abstract boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
index 4089a9b..3e51706 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CommonSubexpressionElimination.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Equivalence;
@@ -35,7 +36,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
index 89b6f2d..9584e8a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/DexConstantOptimizer.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.ConstantCanonicalizer;
import com.android.tools.r8.utils.LazyBox;
@@ -71,7 +72,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
index 28a40a5..7c476ff 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
@@ -126,7 +127,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return code.metadata().mayHaveNewArrayFilled();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java
index a9084b1..c8df326 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/KnownArrayLengthRewriter.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import java.util.Set;
@@ -28,7 +29,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return code.metadata().mayHaveArrayLength();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java
index 42c9c91..a7277a8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/MoveResultRewriter.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
@@ -37,7 +38,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return options.isGeneratingDex() && code.metadata().mayHaveInvokeMethod();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
index eaa0edb..3096671 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Sub;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.WorkList;
@@ -59,7 +60,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
// This is relevant only if a loop may be present, which implies at least 4 blocks.
return appView.options().enableLoopUnrolling
&& code.metadata().mayHaveIf()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
index 28b76ae..43d249f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.utils.CollectionUtils;
@@ -139,7 +140,7 @@
/** Only run this when the rewriting may actually enable more constructor inlining. */
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
if (!appView.hasClassHierarchy()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
index 298a742..f031b5c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/RedundantConstNumberRemover.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.code.IfType;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.utils.LazyBox;
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
@@ -51,7 +52,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
&& !appView.options().testing.forceRedundantConstNumberRemoval) {
// See also b/124152497.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
index 7ebed43..c8455fc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitBranch.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.ListUtils;
@@ -38,7 +39,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
// This is relevant only if there is a diamond followed by an if which is a minimum of 6 blocks.
return code.metadata().mayHaveIf() && code.getBlocks().size() >= 6;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
index b9b34ad..3935555 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ThrowCatchOptimizer.java
@@ -31,6 +31,7 @@
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.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
@@ -54,7 +55,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
index 60ff58e..73fd010 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -46,7 +46,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return appView.enableWholeProgramOptimizations()
&& appView.options().testing.enableCheckCastAndInstanceOfRemoval
&& (code.metadata().mayHaveCheckCast() || code.metadata().mayHaveInstanceOf());
@@ -231,7 +231,8 @@
// type.
if (castType.isClassType()
&& castType.isAlwaysNull(appViewWithLiveness)
- && !outValue.hasDebugUsers()) {
+ && !outValue.hasDebugUsers()
+ && !appView.getSyntheticItems().isFinalized()) {
// Replace all usages of the out-value by null.
it.previous();
Value nullValue = it.insertConstNullInstruction(code, options);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java
index 3ecb8be..c0fb1af 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialGotosCollapser.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import java.util.ArrayList;
import java.util.HashSet;
@@ -87,7 +88,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 46988a7..e204f8e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -178,9 +178,7 @@
// Synthesize virtual methods.
private void synthesizeVirtualMethods(
SyntheticProgramClassBuilder builder, DesugarInvoke desugarInvoke) {
- DexMethod mainMethod =
- appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
-
+ DexMethod mainMethod = descriptor.getMainMethod().withHolder(type, appView.dexItemFactory());
List<DexEncodedMethod> methods = new ArrayList<>(1 + descriptor.bridges.size());
// Synthesize main method.
@@ -198,7 +196,7 @@
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
DexMethod bridgeMethod =
- appView.dexItemFactory().createMethod(type, bridgeProto, descriptor.name);
+ appView.dexItemFactory().createMethod(type, bridgeProto, descriptor.getName());
methods.add(
DexEncodedMethod.syntheticBuilder()
.setMethod(bridgeMethod)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 0c356a2..e3cbc07 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -20,6 +20,10 @@
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
@@ -39,13 +43,11 @@
final String uniqueId;
final DexMethod mainMethod;
- public final DexString name;
- final DexProto erasedProto;
final DexProto enforcedProto;
public final DexMethodHandle implHandle;
- public final List<DexType> interfaces = new ArrayList<>();
- public final Set<DexProto> bridges = Sets.newIdentityHashSet();
+ public final List<DexType> interfaces;
+ public final Set<DexProto> bridges;
public final DexTypeList captures;
// Used for accessibility analysis and few assertions only.
@@ -53,21 +55,21 @@
private final DexType targetHolder;
private LambdaDescriptor() {
- uniqueId = null;
- name = null;
- erasedProto = null;
- enforcedProto = null;
- implHandle = null;
- captures = null;
- targetAccessFlags = null;
- targetHolder = null;
- mainMethod = null;
+ this(null, null, null, null, null, null, null, null, null);
+ }
+
+ public DexProto getErasedProto() {
+ return mainMethod.getProto();
}
public DexMethod getMainMethod() {
return mainMethod;
}
+ public DexString getName() {
+ return mainMethod.getName();
+ }
+
private LambdaDescriptor(
AppView<?> appView,
AppInfoWithClassHierarchy appInfo,
@@ -89,12 +91,11 @@
assert captures != null;
this.mainMethod = appInfo.dexItemFactory().createMethod(mainInterface, erasedProto, name);
this.uniqueId = callSite.getHash();
- this.name = name;
- this.erasedProto = erasedProto;
this.enforcedProto = enforcedProto;
this.implHandle = implHandle;
this.captures = captures;
-
+ this.bridges = Sets.newIdentityHashSet();
+ this.interfaces = new ArrayList<>();
this.interfaces.add(mainInterface);
DexClassAndMethod targetMethod =
context == null ? null : lookupTargetMethod(appView, appInfo, context);
@@ -107,6 +108,27 @@
}
}
+ private LambdaDescriptor(
+ String uniqueId,
+ DexMethod mainMethod,
+ DexProto enforcedProto,
+ DexMethodHandle implHandle,
+ List<DexType> interfaces,
+ Set<DexProto> bridges,
+ DexTypeList captures,
+ MethodAccessFlags targetAccessFlags,
+ DexType targetHolder) {
+ this.uniqueId = uniqueId;
+ this.mainMethod = mainMethod;
+ this.enforcedProto = enforcedProto;
+ this.implHandle = implHandle;
+ this.interfaces = interfaces;
+ this.bridges = bridges;
+ this.captures = captures;
+ this.targetAccessFlags = targetAccessFlags;
+ this.targetHolder = targetHolder;
+ }
+
final DexType getImplReceiverType() {
// The receiver of instance impl-method is captured as the first captured
// value or should be the first argument of the enforced method signature.
@@ -179,7 +201,7 @@
return method.getDefinition().isPublicized() && isInstanceMethod(method);
}
- public final boolean verifyTargetFoundInClass(DexType type) {
+ public boolean verifyTargetFoundInClass(DexType type) {
return targetHolder.isIdenticalTo(type);
}
@@ -189,14 +211,15 @@
}
public void forEachErasedAndEnforcedTypes(BiConsumer<DexType, DexType> consumer) {
- consumer.accept(erasedProto.returnType, enforcedProto.returnType);
+ DexProto erasedProto = getErasedProto();
+ consumer.accept(erasedProto.getReturnType(), enforcedProto.getReturnType());
for (int i = 0; i < enforcedProto.getArity(); i++) {
consumer.accept(erasedProto.getParameter(i), enforcedProto.getParameter(i));
}
}
/** Is a stateless lambda, i.e. lambda does not capture any values */
- final boolean isStateless() {
+ boolean isStateless() {
return captures.isEmpty();
}
@@ -495,4 +518,32 @@
return false;
}
+
+ public LambdaDescriptor rewrittenWithLens(
+ GraphLens lens, GraphLens appliedLens, LensCodeRewriterUtils rewriter) {
+ String newUniqueId = uniqueId;
+ DexMethod newMainMethod = lens.getRenamedMethodSignature(mainMethod, appliedLens);
+ DexProto newEnforcedProto = rewriter.rewriteProto(enforcedProto);
+ DexMethodHandle newImplHandle =
+ rewriter.rewriteDexMethodHandle(
+ implHandle, MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY, mainMethod);
+ List<DexType> newInterfaces =
+ new ArrayList<>(
+ SetUtils.mapLinkedHashSet(interfaces, itf -> lens.lookupType(itf, appliedLens)));
+ Set<DexProto> newBridges = SetUtils.mapIdentityHashSet(bridges, rewriter::rewriteProto);
+ DexTypeList newCaptures = captures.map(capture -> lens.lookupType(capture, appliedLens));
+ MethodAccessFlags newTargetAccessFlags = targetAccessFlags;
+ DexType newTargetHolder =
+ targetHolder != null ? lens.lookupType(targetHolder, appliedLens) : null;
+ return new LambdaDescriptor(
+ newUniqueId,
+ newMainMethod,
+ newEnforcedProto,
+ newImplHandle,
+ newInterfaces,
+ newBridges,
+ newCaptures,
+ newTargetAccessFlags,
+ newTargetHolder);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index df5b8b4..44f0307 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.ValueType;
@@ -181,8 +182,8 @@
DexMethod methodToCall = target.callTarget;
DexType[] capturedTypes = lambda.descriptor.captures.values;
- DexType[] erasedParams = lambda.descriptor.erasedProto.parameters.values;
- DexType erasedReturnType = lambda.descriptor.erasedProto.returnType;
+ DexTypeList erasedParams = lambda.descriptor.getErasedProto().getParameters();
+ DexType erasedReturnType = lambda.descriptor.getErasedProto().getReturnType();
DexType[] enforcedParams = lambda.descriptor.enforcedProto.parameters.values;
DexType enforcedReturnType = lambda.descriptor.enforcedProto.returnType;
if (enforcedReturnType.isPrimitiveType() && mainMethod.getReturnType().isReferenceType()) {
@@ -244,14 +245,14 @@
// Prepare arguments.
int maxLocals = 1; // Local 0 is the lambda/receiver.
- for (int i = 0; i < erasedParams.length; i++) {
+ for (int i = 0; i < erasedParams.size(); i++) {
ValueType valueType = ValueType.fromDexType(mainMethod.getParameters().values[i]);
instructions.add(new CfLoad(valueType, maxLocals));
maxLocals += valueType.requiredRegisters();
DexType expectedParamType = implReceiverAndArgs.get(i + capturedValues);
maxStack +=
prepareParameterValue(
- erasedParams[i], enforcedParams[i], expectedParamType, instructions, factory);
+ erasedParams.get(i), enforcedParams[i], expectedParamType, instructions, factory);
}
CfInvoke invoke =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 064c4b2..1a6c122 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination.RedundantFieldLoadAndStoreEliminationOnCode.ExistingValue;
@@ -83,7 +84,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return appView.options().enableRedundantFieldLoadElimination
&& (code.metadata().mayHaveArrayGet()
|| code.metadata().mayHaveFieldInstruction()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
index 2a90c2e..a9dfc03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/api/InstanceInitializerOutliner.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -16,7 +17,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.DynamicType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -29,13 +29,14 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.conversion.PostMethodProcessor;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.ir.synthetic.NewInstanceSourceCode;
import com.android.tools.r8.shaking.ComputeApiLevelUseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -46,32 +47,21 @@
* Unlike the ApiInvokeOutlinerDesugaring that works on CF, this works on IR to properly replace the
* users of the NewInstance call.
*/
-public class InstanceInitializerOutliner {
+public class InstanceInitializerOutliner extends CodeRewriterPass<AppInfo> {
- private final AppView<?> appView;
private final DexItemFactory factory;
- private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
-
public InstanceInitializerOutliner(AppView<?> appView) {
- this.appView = appView;
+ super(appView);
this.factory = appView.dexItemFactory();
}
- public List<ProgramMethod> getSynthesizedMethods() {
- return synthesizedMethods;
- }
-
- public void rewriteInstanceInitializers(
+ @Override
+ protected CodeRewriterResult rewriteCode(
IRCode code,
- ProgramMethod context,
MethodProcessor methodProcessor,
MethodProcessingContext methodProcessingContext) {
assert !methodProcessor.isPostMethodProcessor();
- // Do not outline from already synthesized methods.
- if (context.getDefinition().isD8R8Synthesized()) {
- return;
- }
Map<NewInstance, Value> rewrittenNewInstances = new IdentityHashMap<>();
ComputedApiLevel minApiLevel = appView.computedMinApiLevel();
InstructionListIterator iterator = code.instructionListIterator();
@@ -104,7 +94,7 @@
continue;
}
// Check if this is already outlined.
- if (isOutlinedAtSameOrLowerLevel(context.getHolder(), apiReferenceLevel)) {
+ if (isOutlinedAtSameOrLowerLevel(code.context().getHolder(), apiReferenceLevel)) {
continue;
}
DexEncodedMethod synthesizedInstanceInitializer =
@@ -125,7 +115,7 @@
rewrittenNewInstances.put(newInstance, outlinedMethodInvoke.outValue());
}
if (rewrittenNewInstances.isEmpty()) {
- return;
+ return CodeRewriterResult.NO_CHANGE;
}
// Scan over NewInstance calls that needs to be outlined. We insert a call to a synthetic method
// with a NewInstance to preserve class-init semantics.
@@ -173,14 +163,10 @@
// the outline again in R8 - but allow inlining of other calls to min api level methods, we have
// to recompute the api level.
if (appView.enableWholeProgramOptimizations()) {
- recomputeApiLevel(context, code);
+ recomputeApiLevel(code.context(), code);
}
- assert code.isConsistentSSA(appView);
- }
-
- public void onLastWaveDone(PostMethodProcessor.Builder postMethodProcessorBuilder) {
- postMethodProcessorBuilder.addAll(synthesizedMethods, appView.graphLens());
+ return CodeRewriterResult.HAS_CHANGED;
}
private boolean canSkipClInit(
@@ -249,9 +235,7 @@
methodProcessor
.getEventConsumer()
.acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext());
- synchronized (synthesizedMethods) {
- synthesizedMethods.add(method);
- }
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
return method.getDefinition();
}
@@ -271,31 +255,56 @@
kinds -> kinds.API_MODEL_OUTLINE,
methodProcessingContext.createUniqueContext(),
appView,
- builder ->
- builder
- .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
- .setProto(proto)
- .setApiLevelForDefinition(appView.computedMinApiLevel())
- .setApiLevelForCode(computedApiLevel)
- .setCode(
- m ->
- ForwardMethodBuilder.builder(appView.dexItemFactory())
- .setConstructorTargetWithNewInstance(targetMethod)
- .setStaticSource(m)
- .build()));
+ builder -> {
+ DynamicType exactDynamicReturnType =
+ DynamicType.createExact(
+ targetMethod
+ .getHolderType()
+ .toTypeElement(appView, Nullability.definitelyNotNull())
+ .asClassType());
+ builder
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setProto(proto)
+ .setApiLevelForDefinition(appView.computedMinApiLevel())
+ .setApiLevelForCode(computedApiLevel)
+ .setCode(
+ m ->
+ ForwardMethodBuilder.builder(appView.dexItemFactory())
+ .setConstructorTargetWithNewInstance(targetMethod)
+ .setStaticSource(m)
+ .build())
+ .setOptimizationInfo(
+ DefaultMethodOptimizationInfo.getInstance()
+ .toMutableOptimizationInfo()
+ .setDynamicType(exactDynamicReturnType));
+ });
methodProcessor
.getEventConsumer()
.acceptInstanceInitializerOutline(method, methodProcessingContext.getMethodContext());
- synchronized (synthesizedMethods) {
- synthesizedMethods.add(method);
- ClassTypeElement exactType =
- targetMethod
- .getHolderType()
- .toTypeElement(appView, Nullability.definitelyNotNull())
- .asClassType();
- OptimizationFeedback.getSimpleFeedback()
- .setDynamicReturnType(method, appView, DynamicType.createExact(exactType));
- }
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
return method.getDefinition();
}
+
+ @Override
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+ if (!appView.options().desugarState.isOn()
+ || !appView.options().apiModelingOptions().enableOutliningOfMethods
+ || !appView.options().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+ return false;
+ }
+ // Only outline in primary optimization pass.
+ if (!methodProcessor.isD8MethodProcessor() && !methodProcessor.isPrimaryMethodProcessor()) {
+ return false;
+ }
+ // Do not outline from already synthesized methods.
+ if (code.context().getDefinition().isD8R8Synthesized()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected String getRewriterId() {
+ return "InstanceInitializerOutliner";
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 814bb26..9f3a782 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
@@ -186,7 +187,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
if (!options.enableEnumValueOptimization || !appView.hasLiveness()) {
return false;
}
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 ea5f65d..36c1bce 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
@@ -122,7 +122,6 @@
@Override
public int getReturnedArgument() {
- assert returnsArgument();
return UNKNOWN_RETURNED_ARGUMENT;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 47f2e03..9b9b529 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -919,6 +919,7 @@
feedback.classInitializerMayBePostponed(method);
} else {
assert options.debug
+ || appView.getSyntheticItems().isFinalized()
|| appView
.getSyntheticItems()
.verifySyntheticLambdaProperty(
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 9617c85..6c7a777 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
@@ -84,6 +84,10 @@
abstractValue.rewrittenWithLens(appView, field.getType(), lens, codeLens), field);
}
+ public void unsetAbstractValue() {
+ abstractValue = AbstractValue.unknown();
+ }
+
@Override
public int getReadBits() {
return readBits;
@@ -111,6 +115,10 @@
this.dynamicType = dynamicType;
}
+ public void unsetDynamicType() {
+ setDynamicType(DynamicType.unknown());
+ }
+
@Override
public boolean isDead() {
return (flags & FLAGS_IS_DEAD) != 0;
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 602b555..300c8c0 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
@@ -33,7 +33,6 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OptionalBool;
import java.util.BitSet;
-import java.util.Collections;
import java.util.Set;
public class MutableMethodOptimizationInfo extends MethodOptimizationInfo
@@ -297,6 +296,10 @@
return this;
}
+ public void unsetArgumentInfos() {
+ argumentInfos = CallSiteOptimizationInfo.top();
+ }
+
@Override
public ClassInlinerMethodConstraint getClassInlinerMethodConstraint() {
return classInlinerConstraint;
@@ -630,11 +633,16 @@
}
void markInitializesClassesOnNormalExit(Set<DexType> initializedClassesOnNormalExit) {
- this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
+ if (initializedClassesOnNormalExit.isEmpty()) {
+ unsetInitializedClassesOnNormalExit();
+ } else {
+ this.initializedClassesOnNormalExit = initializedClassesOnNormalExit;
+ }
}
void unsetInitializedClassesOnNormalExit() {
- initializedClassesOnNormalExit = Collections.emptySet();
+ initializedClassesOnNormalExit =
+ DefaultMethodOptimizationInfo.getInstance().getInitializedClassesOnNormalExit();
}
void markReturnsArgument(int returnedArgumentIndex) {
@@ -710,7 +718,7 @@
setDynamicType(newDynamicType);
}
- private MutableMethodOptimizationInfo setDynamicType(DynamicType dynamicType) {
+ public MutableMethodOptimizationInfo setDynamicType(DynamicType dynamicType) {
assert !dynamicType.hasDynamicUpperBoundType()
|| !dynamicType.asDynamicTypeWithUpperBound().getDynamicUpperBoundType().isPrimitiveType();
this.dynamicType = dynamicType;
@@ -767,6 +775,30 @@
return isFlagSet(RETURN_VALUE_HAS_BEEN_PROPAGATED_FLAG);
}
+ @SuppressWarnings("ReferenceEquality")
+ public boolean isEffectivelyDefault() {
+ DefaultMethodOptimizationInfo top = DefaultMethodOptimizationInfo.getInstance();
+ return argumentInfos == top.getArgumentInfos()
+ && initializedClassesOnNormalExit == top.getInitializedClassesOnNormalExit()
+ && returnedArgument == top.getReturnedArgument()
+ && abstractReturnValue == top.getAbstractReturnValue()
+ && classInlinerConstraint == top.getClassInlinerMethodConstraint()
+ && convertCheckNotNull == top.isConvertCheckNotNull()
+ && enumUnboxerMethodClassification == top.getEnumUnboxerMethodClassification()
+ && dynamicType == top.getDynamicType()
+ && inlining == InlinePreference.Default
+ && isReturnValueUsed == top.isReturnValueUsed()
+ && bridgeInfo == top.getBridgeInfo()
+ && instanceInitializerInfoCollection.isEmpty()
+ && nonNullParamOrThrow == top.getNonNullParamOrThrow()
+ && nonNullParamOnNormalExits == top.getNonNullParamOnNormalExits()
+ && simpleInliningConstraint == top.getSimpleInliningConstraint()
+ && maxRemovedAndroidLogLevel == top.getMaxRemovedAndroidLogLevel()
+ && parametersWithBitwiseOperations == top.getParametersWithBitwiseOperations()
+ && unusedArguments == top.getUnusedArguments()
+ && flags == DEFAULT_FLAGS;
+ }
+
@Override
public boolean isMutableOptimizationInfo() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java
new file mode 100644
index 0000000..a336a3d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationInfoRemover.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2024, 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.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Clears any optimization info on fields and methods that need lens code rewriting. This avoids the
+ * need to lens code rewrite such optimization info in repackaging and other optimizations where the
+ * optimization info is mostly unused, since no more optimizations passes will be run.
+ */
+public class OptimizationInfoRemover {
+
+ public static void run(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ExecutorService executorService)
+ throws ExecutionException {
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ OptimizationInfoRemover::processClass,
+ appView.options().getThreadingModule(),
+ executorService);
+ }
+
+ private static void processClass(DexProgramClass clazz) {
+ for (DexEncodedField field : clazz.fields()) {
+ processField(field);
+ }
+ for (DexEncodedMethod method : clazz.methods()) {
+ processMethod(method);
+ }
+ }
+
+ private static void processField(DexEncodedField field) {
+ MutableFieldOptimizationInfo optimizationInfo =
+ field.getOptimizationInfo().asMutableFieldOptimizationInfo();
+ if (optimizationInfo == null) {
+ return;
+ }
+ optimizationInfo.unsetAbstractValue();
+ optimizationInfo.unsetDynamicType();
+ }
+
+ private static void processMethod(DexEncodedMethod method) {
+ MutableMethodOptimizationInfo optimizationInfo =
+ method.getOptimizationInfo().asMutableMethodOptimizationInfo();
+ if (optimizationInfo == null) {
+ return;
+ }
+ optimizationInfo.unsetAbstractReturnValue();
+ optimizationInfo.unsetArgumentInfos();
+ optimizationInfo.unsetDynamicType();
+ optimizationInfo.unsetInitializedClassesOnNormalExit();
+ optimizationInfo.unsetInstanceInitializerInfoCollection();
+ if (optimizationInfo.isEffectivelyDefault()) {
+ method.unsetOptimizationInfo();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index bc16337..eb0aa0d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
@@ -95,7 +96,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return options.enableStringConcatenationOptimization
&& !isDebugMode(code.context())
&& (code.metadata().mayHaveNewInstance()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 3eeaae6..ad4c24f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.CodeRewriterPass;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
@@ -52,7 +53,7 @@
}
@Override
- protected boolean shouldRewriteCode(IRCode code) {
+ protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
return !isDebugMode(code.context());
}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index 056b7fa..29c7ca6 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -36,6 +36,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ComparatorUtils;
+import com.android.tools.r8.utils.FastMapUtils;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.RetracerForCodePrinting;
@@ -200,6 +201,15 @@
return TryCatchTable::specify;
}
+ public TryCatchTable rewriteWithLens(GraphLens graphLens, GraphLens codeLens) {
+ Int2ReferenceMap<CatchHandlers<Integer>> newTryCatchHandlers =
+ FastMapUtils.mapInt2ReferenceOpenHashMapOrElse(
+ tryCatchHandlers,
+ (block, blockHandlers) -> blockHandlers.rewriteWithLens(graphLens, codeLens),
+ null);
+ return newTryCatchHandlers != null ? new TryCatchTable(newTryCatchHandlers) : this;
+ }
+
private static void specify(StructuralSpecification<TryCatchTable, ?> spec) {
spec.withInt2CustomItemMap(
s -> s.tryCatchHandlers,
@@ -774,6 +784,24 @@
metadataMap);
}
+ public LirCode<EV> newCodeWithRewrittenTryCatchTable(TryCatchTable rewrittenTryCatchTable) {
+ if (rewrittenTryCatchTable == tryCatchTable) {
+ return this;
+ }
+ return new LirCode<>(
+ irMetadata,
+ constants,
+ positionTable,
+ argumentCount,
+ instructions,
+ instructionCount,
+ rewrittenTryCatchTable,
+ debugLocalInfoTable,
+ strategyInfo,
+ useDexEstimationStrategy,
+ metadataMap);
+ }
+
public LirCode<EV> rewriteWithSimpleLens(
ProgramMethod context, AppView<?> appView, LensCodeRewriterUtils rewriterUtils) {
GraphLens graphLens = appView.graphLens();
diff --git a/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java b/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
index d093823..1a4d065 100644
--- a/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/SimpleLensLirRewriter.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.lightir;
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
@@ -17,6 +20,8 @@
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.code.Opcodes;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.lightir.LirBuilder.RecordFieldValuesPayload;
+import com.android.tools.r8.lightir.LirCode.TryCatchTable;
import com.android.tools.r8.utils.ArrayUtils;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
@@ -68,6 +73,12 @@
addRewrittenMapping(callSite, helper.rewriteCallSite(callSite, context));
}
+ public void onMethodHandleReference(DexMethodHandle methodHandle) {
+ addRewrittenMapping(
+ methodHandle,
+ helper.rewriteDexMethodHandle(methodHandle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, context));
+ }
+
public void onProtoReference(DexProto proto) {
addRewrittenMapping(proto, helper.rewriteProto(proto));
}
@@ -143,7 +154,8 @@
public LirCode<EV> rewrite() {
LirCode<EV> rewritten = rewriteConstantPoolAndScanForTypeChanges(getCode());
- return rewriteInstructionsWithInvokeTypeChanges(rewritten);
+ rewritten = rewriteInstructionsWithInvokeTypeChanges(rewritten);
+ return rewriteTryCatchTable(rewritten);
}
private LirCode<EV> rewriteConstantPoolAndScanForTypeChanges(LirCode<EV> code) {
@@ -152,12 +164,16 @@
// fields/methods that need to be examined.
boolean hasPotentialRewrittenMethod = false;
for (LirConstant constant : code.getConstantPool()) {
+ // RecordFieldValuesPayload is lowered to NewArrayEmpty before lens code rewriting any LIR.
+ assert !(constant instanceof RecordFieldValuesPayload);
if (constant instanceof DexType) {
onTypeReference((DexType) constant);
} else if (constant instanceof DexField) {
onFieldReference((DexField) constant);
} else if (constant instanceof DexCallSite) {
onCallSiteReference((DexCallSite) constant);
+ } else if (constant instanceof DexMethodHandle) {
+ onMethodHandleReference((DexMethodHandle) constant);
} else if (constant instanceof DexProto) {
onProtoReference((DexProto) constant);
} else if (!hasPotentialRewrittenMethod && constant instanceof DexMethod) {
@@ -265,4 +281,13 @@
byteWriter.toByteArray());
return newCode;
}
+
+ private LirCode<EV> rewriteTryCatchTable(LirCode<EV> code) {
+ TryCatchTable tryCatchTable = code.getTryCatchTable();
+ if (tryCatchTable == null) {
+ return code;
+ }
+ TryCatchTable newTryCatchTable = tryCatchTable.rewriteWithLens(graphLens, codeLens);
+ return code.newCodeWithRewrittenTryCatchTable(newTryCatchTable);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 7e91d18..9e966d0 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -116,5 +116,6 @@
},
appView.options().getThreadingModule(),
executorService);
+ appView.setGenericSignaturesLens(appView.graphLens());
}
}
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 3d6fc32..5272500 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.graph.fixup.TreeFixerBase;
import com.android.tools.r8.graph.lens.NestedGraphLens;
import com.android.tools.r8.naming.Minifier.MinificationPackageNamingStrategy;
+import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.repackaging.RepackagingLens.Builder;
import com.android.tools.r8.shaking.AnnotationFixer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -78,6 +79,7 @@
if (lens != null) {
appView.rewriteWithLensAndApplication(lens, appBuilder.build(), executorService, timing);
appView.testing().repackagingLensConsumer.accept(appView.dexItemFactory(), lens);
+ new GenericSignatureRewriter(appView).run(appView.appInfo().classes(), executorService);
}
appView.notifyOptimizationFinishedForTesting();
timing.end();
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 93ac4f4..e634f97 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Definition;
+import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
@@ -316,8 +317,8 @@
pruneMethods(previous.bootstrapMethods, prunedItems, tasks),
pruneMethods(previous.virtualMethodsTargetedByInvokeDirect, prunedItems, tasks),
pruneMethods(previous.liveMethods, prunedItems, tasks),
- previous.fieldAccessInfoCollection,
- previous.methodAccessInfoCollection.withoutPrunedItems(prunedItems),
+ previous.fieldAccessInfoCollection.withoutPrunedItems(prunedItems),
+ previous.methodAccessInfoCollection.withoutPrunedContexts(prunedItems),
previous.objectAllocationInfoCollection.withoutPrunedItems(prunedItems),
pruneCallSites(previous.callSites, prunedItems),
extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
@@ -1090,12 +1091,19 @@
return appInfoWithLiveness;
}
+ public AppInfoWithLiveness rebuildWithLiveness(DexApplication application) {
+ return rebuildWithLiveness(getSyntheticItems().commit(application));
+ }
+
public AppInfoWithLiveness rebuildWithLiveness(CommittedItems committedItems) {
return new AppInfoWithLiveness(this, committedItems);
}
public AppInfoWithLiveness rewrittenWithLens(
- DirectMappedDexApplication application, NonIdentityGraphLens lens, Timing timing) {
+ DirectMappedDexApplication application,
+ NonIdentityGraphLens lens,
+ GraphLens appliedLens,
+ Timing timing) {
assert checkIfObsolete();
// Switchmap classes should never be affected by renaming.
@@ -1126,7 +1134,8 @@
lens.rewriteReferences(liveMethods),
fieldAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing),
methodAccessInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing),
- objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens, timing),
+ objectAllocationInfoCollection.rewrittenWithLens(
+ definitionSupplier, lens, appliedLens, timing),
lens.rewriteCallSites(callSites, definitionSupplier, timing),
keepInfo.rewrite(definitionSupplier, lens, application.options, timing),
// Take any rule in case of collisions.
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index f0080f3..dc0a4ac 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -84,9 +84,11 @@
return hasher.hash();
}
if (intermediate) {
- // If in intermediate mode, include the context type as sharing is restricted to within a
- // single context.
- getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
+ // If in intermediate mode, include the *input* context type to restrict sharing.
+ // This restricts sharing to only allow sharing the synthetics with the same *input* context.
+ // If the synthetic is itself an input from a previous compilation it is restricted to share
+ // within its own context only. The input context should not be mapped to an equivalence type.
+ getContext().getSynthesizingInputContext(intermediate).hash(hasher);
}
hasher.putInt(context.getFeatureSplit().hashCode());
internalComputeHash(hasher, map);
@@ -103,7 +105,6 @@
return compareTo(other, includeContext, graphLens, classToFeatureSplitMap) == 0;
}
- @SuppressWarnings("ReferenceEquality")
int compareTo(
D other,
boolean includeContext,
@@ -138,10 +139,12 @@
if (graphLens.isNonIdentityLens()) {
DexType thisOrigType = graphLens.getOriginalType(thisType);
DexType otherOrigType = graphLens.getOriginalType(otherType);
- if (thisType != thisOrigType || otherType != otherOrigType) {
+ if (thisType.isNotIdenticalTo(thisOrigType) || otherType.isNotIdenticalTo(otherOrigType)) {
map =
t -> {
- if (t == otherType || t == thisOrigType || t == otherOrigType) {
+ if (DexType.identical(t, otherType)
+ || DexType.identical(t, thisOrigType)
+ || DexType.identical(t, otherOrigType)) {
return thisType;
}
return t;
@@ -149,7 +152,7 @@
}
}
if (map == null) {
- map = t -> t == otherType ? thisType : t;
+ map = t -> otherType.isIdenticalTo(t) ? thisType : t;
}
return internalCompareTo(other, map);
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
index 8b3a049..1c83db6 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/Tracer.java
@@ -512,7 +512,7 @@
}
for (DexProto bridgeProto : descriptor.bridges) {
DexEncodedMethod bridgeMethod =
- interfaceDefinition.lookupMethod(bridgeProto, descriptor.name);
+ interfaceDefinition.lookupMethod(bridgeProto, descriptor.getName());
if (bridgeMethod != null) {
registerInvokeInterface(bridgeMethod.getReference());
}
diff --git a/src/main/java/com/android/tools/r8/utils/DominatorChecker.java b/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
index c2fd047..d1dc853 100644
--- a/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
+++ b/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Set;
@@ -32,20 +31,20 @@
}
class TraversingDominatorChecker implements DominatorChecker {
- private final BasicBlock sourceBlock;
- private final BasicBlock destBlock;
+ private final BasicBlock subgraphEntryBlock;
+ private final BasicBlock subgraphExitBlock;
private final Set<BasicBlock> knownDominators;
- private final ArrayDeque<BasicBlock> workQueue = new ArrayDeque<>();
- private final Set<BasicBlock> visited;
+ private final WorkList<BasicBlock> workList = WorkList.newIdentityWorkList();
private BasicBlock prevTargetBlock;
private TraversingDominatorChecker(
- BasicBlock sourceBlock, BasicBlock destBlock, Set<BasicBlock> knownDominators) {
- this.sourceBlock = sourceBlock;
- this.destBlock = destBlock;
+ BasicBlock subgraphEntryBlock,
+ BasicBlock subgraphExitBlock,
+ Set<BasicBlock> knownDominators) {
+ this.subgraphEntryBlock = subgraphEntryBlock;
+ this.subgraphExitBlock = subgraphExitBlock;
this.knownDominators = knownDominators;
- this.visited = Sets.newIdentityHashSet();
- prevTargetBlock = destBlock;
+ prevTargetBlock = subgraphExitBlock;
}
@Override
@@ -60,7 +59,7 @@
if (firstSplittingBlock.hasUniqueSuccessor()) {
do {
// knownDominators prevents firstSplittingBlock from being destBlock.
- assert firstSplittingBlock != destBlock;
+ assert firstSplittingBlock != subgraphExitBlock;
firstSplittingBlock = firstSplittingBlock.getUniqueSuccessor();
} while (firstSplittingBlock.hasUniqueSuccessor());
@@ -73,13 +72,12 @@
boolean ret;
// Since we know the previously checked block is a dominator, narrow the check by using it for
// either sourceBlock or destBlock.
- if (visited.contains(targetBlock)) {
- visited.clear();
- ret =
- checkWithTraversal(prevTargetBlock, destBlock, firstSplittingBlock, visited, workQueue);
+ if (workList.isSeen(targetBlock)) {
+ workList.clearSeen();
+ ret = checkWithTraversal(prevTargetBlock, subgraphExitBlock, firstSplittingBlock, workList);
prevTargetBlock = firstSplittingBlock;
} else {
- ret = checkWithTraversal(sourceBlock, prevTargetBlock, targetBlock, visited, workQueue);
+ ret = checkWithTraversal(subgraphEntryBlock, prevTargetBlock, targetBlock, workList);
prevTargetBlock = targetBlock;
}
if (ret) {
@@ -93,70 +91,71 @@
return ret;
}
+ /**
+ * Within the subgraph defined by the given entry/exit blocks, returns whether targetBlock
+ * dominates the exit block.
+ */
private static boolean checkWithTraversal(
- BasicBlock sourceBlock,
- BasicBlock destBlock,
+ BasicBlock subgraphEntryBlock,
+ BasicBlock subgraphExitBlock,
BasicBlock targetBlock,
- Set<BasicBlock> visited,
- ArrayDeque<BasicBlock> workQueue) {
- assert workQueue.isEmpty();
+ WorkList<BasicBlock> workList) {
+ assert workList.isEmpty();
- visited.add(targetBlock);
-
- workQueue.addAll(destBlock.getPredecessors());
- do {
- BasicBlock curBlock = workQueue.removeLast();
- if (!visited.add(curBlock)) {
- continue;
- }
- if (curBlock == sourceBlock) {
- // There is a path from sourceBlock -> destBlock that does not go through block.
+ workList.markAsSeen(targetBlock);
+ workList.addIfNotSeen(subgraphExitBlock.getPredecessors());
+ while (!workList.isEmpty()) {
+ BasicBlock curBlock = workList.removeLast();
+ if (curBlock == subgraphEntryBlock) {
+ // There is a path from subgraphExitBlock -> subgraphEntryBlock that does not go through
+ // targetBlock.
return false;
}
- assert !curBlock.isEntry() : "sourceBlock did not dominate destBlock";
- workQueue.addAll(curBlock.getPredecessors());
- } while (!workQueue.isEmpty());
+ assert !curBlock.isEntry() : "subgraphEntryBlock did not dominate subgraphExitBlock";
+ workList.addIfNotSeen(curBlock.getPredecessors());
+ }
return true;
}
}
- static DominatorChecker create(BasicBlock sourceBlock, BasicBlock destBlock) {
+ static DominatorChecker create(BasicBlock subgraphEntryBlock, BasicBlock subgraphExitBlock) {
// Fast-path: blocks are the same.
// As of Nov 2023: in Chrome for String.format() optimization, this covers 77% of cases.
- if (sourceBlock == destBlock) {
- return new PrecomputedDominatorChecker(Collections.singleton(sourceBlock));
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ return new PrecomputedDominatorChecker(Collections.singleton(subgraphEntryBlock));
}
- // Shrink the subgraph by moving sourceBlock forward to the first block with multiple
+ // Shrink the subgraph by moving subgraphEntryBlock forward to the first block with multiple
// successors.
Set<BasicBlock> headAndTailDominators = Sets.newIdentityHashSet();
- headAndTailDominators.add(sourceBlock);
- while (sourceBlock.hasUniqueSuccessor()) {
- sourceBlock = sourceBlock.getUniqueSuccessor();
- if (!headAndTailDominators.add(sourceBlock)) {
+ headAndTailDominators.add(subgraphEntryBlock);
+ while (subgraphEntryBlock.hasUniqueSuccessor()) {
+ subgraphEntryBlock = subgraphEntryBlock.getUniqueSuccessor();
+ if (!headAndTailDominators.add(subgraphEntryBlock)) {
// Hit an infinite loop. Code would not verify in this case.
assert false;
return FALSE_CHECKER;
}
- if (sourceBlock == destBlock) {
+ if (subgraphEntryBlock == subgraphExitBlock) {
// As of Nov 2023: in Chrome for String.format() optimization, a linear path from
// source->dest was 14% of cases.
return new PrecomputedDominatorChecker(headAndTailDominators);
}
}
- if (sourceBlock.getSuccessors().isEmpty()) {
+ if (subgraphEntryBlock.getSuccessors().isEmpty()) {
return FALSE_CHECKER;
}
- // Shrink the subgraph by moving destBlock back to the first block with multiple predecessors.
- headAndTailDominators.add(destBlock);
- while (destBlock.hasUniquePredecessor()) {
- destBlock = destBlock.getUniquePredecessor();
- if (!headAndTailDominators.add(destBlock)) {
- if (sourceBlock == destBlock) {
- // This normally happens when moving sourceBlock forwards, but when moving destBlock
- // backwards when sourceBlock has multiple successors.
+ // Shrink the subgraph by moving subgraphExitBlock back to the first block with multiple
+ // predecessors.
+ headAndTailDominators.add(subgraphExitBlock);
+ while (subgraphExitBlock.hasUniquePredecessor()) {
+ subgraphExitBlock = subgraphExitBlock.getUniquePredecessor();
+ if (!headAndTailDominators.add(subgraphExitBlock)) {
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ // This normally happens when moving subgraphEntryBlock forwards, but can also occur when
+ // moving subgraphExitBlock backwards when subgraphEntryBlock has multiple successors.
return new PrecomputedDominatorChecker(headAndTailDominators);
}
// Hit an infinite loop. Code would not verify in this case.
@@ -165,10 +164,28 @@
}
}
- if (destBlock.isEntry()) {
+ if (subgraphExitBlock.isEntry()) {
return FALSE_CHECKER;
}
- return new TraversingDominatorChecker(sourceBlock, destBlock, headAndTailDominators);
+ return new TraversingDominatorChecker(
+ subgraphEntryBlock, subgraphExitBlock, headAndTailDominators);
+ }
+
+ /**
+ * Returns whether targetBlock dominates subgraphExitBlock by performing a depth-first traversal
+ * from subgraphExitBlock to subgraphEntryBlock with targetBlock removed from the graph.
+ */
+ @SuppressWarnings("InconsistentOverloads")
+ static boolean check(
+ BasicBlock subgraphEntryBlock, BasicBlock subgraphExitBlock, BasicBlock targetBlock) {
+ if (targetBlock == subgraphExitBlock) {
+ return true;
+ }
+ if (subgraphEntryBlock == subgraphExitBlock) {
+ return false;
+ }
+ return TraversingDominatorChecker.checkWithTraversal(
+ subgraphEntryBlock, subgraphExitBlock, targetBlock, WorkList.newIdentityWorkList());
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/FastMapUtils.java b/src/main/java/com/android/tools/r8/utils/FastMapUtils.java
new file mode 100644
index 0000000..e64f202
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FastMapUtils.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.Iterator;
+import java.util.function.Function;
+
+public class FastMapUtils {
+
+ public static <V> Int2ReferenceMap<V> destructiveMapValues(
+ Int2ReferenceMap<V> map, Function<V, V> valueMapper) {
+ Iterator<Int2ReferenceMap.Entry<V>> iterator = map.int2ReferenceEntrySet().iterator();
+ while (iterator.hasNext()) {
+ Int2ReferenceMap.Entry<V> entry = iterator.next();
+ V newValue = valueMapper.apply(entry.getValue());
+ if (newValue != null) {
+ entry.setValue(newValue);
+ } else {
+ iterator.remove();
+ }
+ }
+ return map;
+ }
+
+ public static <V> Int2ReferenceMap<V> mapInt2ReferenceOpenHashMapOrElse(
+ Int2ReferenceMap<V> map,
+ IntObjToObjFunction<V, V> valueMapper,
+ Int2ReferenceMap<V> defaultValue) {
+ Int2ReferenceMap<V> newMap = null;
+ Iterator<Int2ReferenceMap.Entry<V>> iterator = map.int2ReferenceEntrySet().iterator();
+ while (iterator.hasNext()) {
+ Int2ReferenceMap.Entry<V> entry = iterator.next();
+ int key = entry.getIntKey();
+ V value = entry.getValue();
+ V newValue = valueMapper.apply(key, value);
+ if (newMap == null) {
+ if (newValue == value) {
+ continue;
+ }
+ // This is the first entry where the value has been changed. Create the new map and copy
+ // over previous entries that did not change.
+ Int2ReferenceMap<V> newFinalMap = new Int2ReferenceOpenHashMap<>(map.size());
+ CollectionUtils.forEachUntilExclusive(
+ map.int2ReferenceEntrySet(),
+ previousEntry -> newFinalMap.put(previousEntry.getIntKey(), previousEntry.getValue()),
+ entry);
+ newMap = newFinalMap;
+ }
+ if (newValue != null) {
+ newMap.put(key, newValue);
+ } else {
+ iterator.remove();
+ }
+ }
+ return newMap != null ? newMap : defaultValue;
+ }
+}
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 011fbfb..0dc92d1 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -65,14 +65,11 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
-import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
-import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.desugar.TypeRewriter;
import com.android.tools.r8.ir.desugar.TypeRewriter.MachineTypeRewriter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
@@ -80,9 +77,6 @@
import com.android.tools.r8.ir.desugar.nest.Nest;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
-import com.android.tools.r8.lightir.IR2LirConverter;
-import com.android.tools.r8.lightir.LirCode;
-import com.android.tools.r8.lightir.LirStrategy;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapConsumer;
import com.android.tools.r8.naming.MapVersion;
@@ -133,8 +127,6 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
@@ -2200,56 +2192,9 @@
private LirPhase currentPhase = LirPhase.PRE;
- public void enterLirSupportedPhase(AppView<?> appView, ExecutorService executorService)
- throws ExecutionException {
+ public void enterLirSupportedPhase() {
assert isPreLirPhase();
currentPhase = LirPhase.SUPPORTED;
- if (!canUseLir(appView)) {
- return;
- }
- // Convert code objects to LIR.
- ThreadUtils.processItems(
- appView.appInfo().classes(),
- clazz -> {
- // TODO(b/225838009): Also convert instance initializers to LIR, by adding support for
- // computing the inlining constraint for LIR and using that in the class mergers, and
- // class initializers, by updating the concatenation of clinits in horizontal class
- // merging.
- clazz.forEachProgramMethodMatching(
- method ->
- method.hasCode()
- && !method.isInitializer()
- && !appView.isCfByteCodePassThrough(method),
- method -> {
- IRCode code =
- method.buildIR(appView, MethodConversionOptions.forLirPhase(appView));
- LirCode<Integer> lirCode =
- IR2LirConverter.translate(
- code,
- BytecodeMetadataProvider.empty(),
- LirStrategy.getDefaultStrategy().getEncodingStrategy(),
- appView.options());
- // TODO(b/312890994): Setting a custom code lens is only needed until we convert
- // code objects to LIR before we create the first code object with a custom code
- // lens (horizontal class merging).
- GraphLens codeLens = method.getDefinition().getCode().getCodeLens(appView);
- if (codeLens != appView.codeLens()) {
- lirCode =
- new LirCode<>(lirCode) {
- @Override
- public GraphLens getCodeLens(AppView<?> appView) {
- return codeLens;
- }
- };
- }
- method.setCode(lirCode, appView);
- });
- },
- appView.options().getThreadingModule(),
- executorService);
- // Conversion to LIR via IR will allocate type elements.
- // They are not needed after construction so remove them again.
- appView.dexItemFactory().clearTypeElementsCache();
}
public void exitLirSupportedPhase() {
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 be05c5d..a7c3366 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -6,9 +6,11 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@@ -115,7 +117,7 @@
return builder.build();
}
- public static <T, S> Set<T> mapIdentityHashSet(Set<S> set, Function<S, T> fn) {
+ public static <T, S> Set<T> mapIdentityHashSet(Collection<S> set, Function<S, T> fn) {
Set<T> out = newIdentityHashSet(set.size());
for (S element : set) {
out.add(fn.apply(element));
@@ -123,6 +125,14 @@
return out;
}
+ public static <T, S> Set<T> mapLinkedHashSet(Collection<S> set, Function<S, T> fn) {
+ Set<T> out = new LinkedHashSet<>(set.size());
+ for (S element : set) {
+ out.add(fn.apply(element));
+ }
+ return out;
+ }
+
public static <T> T removeFirst(Set<T> set) {
T element = set.iterator().next();
set.remove(element);
diff --git a/src/main/java/com/android/tools/r8/utils/ValueUtils.java b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
index 68d3364..9082be5 100644
--- a/src/main/java/com/android/tools/r8/utils/ValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
@@ -57,12 +57,15 @@
public static final class ArrayValues {
private List<Value> elementValues;
private ArrayPut[] arrayPutsByIndex;
+ private final Value arrayValue;
- private ArrayValues(List<Value> elementValues) {
+ private ArrayValues(Value arrayValue, List<Value> elementValues) {
+ this.arrayValue = arrayValue;
this.elementValues = elementValues;
}
- private ArrayValues(ArrayPut[] arrayPutsByIndex) {
+ private ArrayValues(Value arrayValue, ArrayPut[] arrayPutsByIndex) {
+ this.arrayValue = arrayValue;
this.arrayPutsByIndex = arrayPutsByIndex;
}
@@ -83,6 +86,28 @@
public int size() {
return elementValues != null ? elementValues.size() : arrayPutsByIndex.length;
}
+
+ public boolean containsHoles() {
+ for (ArrayPut arrayPut : arrayPutsByIndex) {
+ if (arrayPut == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public ArrayPut[] getArrayPutsByIndex() {
+ assert arrayPutsByIndex != null;
+ return arrayPutsByIndex;
+ }
+
+ public Value getArrayValue() {
+ return arrayValue;
+ }
+
+ public Instruction getDefinition() {
+ return arrayValue.definition;
+ }
}
/**
@@ -92,12 +117,8 @@
* 1) The Array has a single users (other than array-puts)
* * This constraint is to ensure other users do not modify the array.
* * When users are in different blocks, their order is hard to know.
- * 2) The array size is a constant.
+ * 2) The array size is a constant and non-negative.
* 3) All array-put instructions have constant and unique indices.
- * * With the exception of array-puts that are in the same block as singleUser, in which case
- * non-unique puts are allowed.
- * * Indices must be unique in other blocks because order is hard to know when multiple blocks
- * are concerned (and reassignment is rare anyways).
* 4) The array-put instructions are guaranteed to be executed before singleUser.
* </pre>
*
@@ -106,7 +127,6 @@
* @return The computed array values, or null if they could not be determined.
*/
public static ArrayValues computeSingleUseArrayValues(Value arrayValue, Instruction singleUser) {
- assert singleUser == null || arrayValue.uniqueUsers().contains(singleUser);
TypeElement arrayType = arrayValue.getType();
if (!arrayType.isArrayType() || arrayValue.hasDebugUsers() || arrayValue.isPhi()) {
return null;
@@ -121,7 +141,7 @@
if (!arrayValue.hasSingleUniqueUser() || arrayValue.hasPhiUsers()) {
return null;
}
- return new ArrayValues(newArrayFilled.inValues());
+ return new ArrayValues(arrayValue, newArrayFilled.inValues());
} else if (newArrayEmpty == null) {
return null;
}
@@ -132,28 +152,83 @@
return null;
}
- if (singleUser == null) {
- for (Instruction user : arrayValue.uniqueUsers()) {
- ArrayPut arrayPut = user.asArrayPut();
- if (arrayPut == null || arrayPut.array() != arrayValue || arrayPut.value() == arrayValue) {
- if (singleUser == null) {
- singleUser = user;
- } else {
- return null;
- }
+ return computeArrayValuesInternal(newArrayEmpty, arraySize, singleUser, false);
+ }
+
+ /**
+ * Determines the values for the given array at the point the last element is assigned.
+ *
+ * <pre>
+ * Returns null under the following conditions:
+ * * The array has a non-const, negative, or abnormally large size.
+ * * An array-put with non-constant index exists.
+ * * An array-put with an out-of-bounds index exists.
+ * * An array-put for the last index is not found.
+ * * An array-put is found after the last-index array-put.
+ * * An array-put is found where the array and value are the same: arr[index] = arr;
+ * * There are multiple array-put instructions for the same index.
+ * </pre>
+ */
+ public static ArrayValues computeInitialArrayValues(NewArrayEmpty newArrayEmpty) {
+ int arraySize = newArrayEmpty.sizeIfConst();
+ if (arraySize < 0 || arraySize > MAX_ARRAY_SIZE) {
+ // Array is non-const size.
+ return null;
+ }
+
+ Value arrayValue = newArrayEmpty.outValue();
+ // Find array-put for the last element, as well as blocks for other array users.
+ ArrayPut lastArrayPut = null;
+ int lastIndex = arraySize - 1;
+ for (Instruction user : arrayValue.uniqueUsers()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut != null && arrayPut.array() == arrayValue) {
+ int index = arrayPut.indexOrDefault(-1);
+ if (index == lastIndex) {
+ lastArrayPut = arrayPut;
+ break;
}
}
}
+ if (lastArrayPut == null) {
+ return null;
+ }
+ // Find all array-puts up until the last one.
+ // Also checks that no array-puts appear after the last one.
+ ArrayValues ret = computeArrayValuesInternal(newArrayEmpty, arraySize, lastArrayPut, true);
+ if (ret == null) {
+ return null;
+ }
+ // Since the last array-put is used as firstUser, it will not already be in arrayPutsByIndex.
+ if (ret.arrayPutsByIndex[lastIndex] != null) {
+ return null;
+ }
+ ret.arrayPutsByIndex[lastIndex] = lastArrayPut;
+ return ret;
+ }
+
+ private static ArrayValues computeArrayValuesInternal(
+ NewArrayEmpty newArrayEmpty, int arraySize, Instruction firstUser, boolean allowOtherUsers) {
ArrayPut[] arrayPutsByIndex = new ArrayPut[arraySize];
- BasicBlock usageBlock = singleUser.getBlock();
+ Value arrayValue = newArrayEmpty.outValue();
+ BasicBlock usageBlock = firstUser.getBlock();
+
+ // Collect array-puts from non-usage blocks, and (optionally) check for multiple users.
for (Instruction user : arrayValue.uniqueUsers()) {
ArrayPut arrayPut = user.asArrayPut();
- if (arrayPut == null || arrayPut.array() != arrayValue || arrayPut.value() == arrayValue) {
- if (user == singleUser) {
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ if (user == firstUser) {
continue;
}
// Found a second non-array-put user.
+ if (allowOtherUsers) {
+ continue;
+ }
+ return null;
+ } else if (arrayPut.value() == arrayValue) {
+ // An array that contains itself is uncommon and hard to reason about.
+ // e.g.: arr[0] = arr;
return null;
}
// Process same-block instructions later.
@@ -168,8 +243,10 @@
arrayPutsByIndex[index] = arrayPut;
}
- // Ensure that all paths from new-array-empty to |usage| contain all array-put instructions.
- DominatorChecker dominatorChecker = DominatorChecker.create(definition.getBlock(), usageBlock);
+ // Ensure that all paths from new-array-empty's block to |usage|'s block contain all array-put
+ // instructions.
+ DominatorChecker dominatorChecker =
+ DominatorChecker.create(newArrayEmpty.getBlock(), usageBlock);
// Visit in reverse order because array-puts generally appear in order, and DominatorChecker's
// cache is more effective when visiting in reverse order.
for (int i = arraySize - 1; i >= 0; --i) {
@@ -179,17 +256,18 @@
}
}
- boolean seenSingleUser = false;
+ // Collect array-puts from the usage block, and ensure no array-puts come after the first user.
+ boolean seenFirstUser = false;
for (Instruction inst : usageBlock.getInstructions()) {
- if (inst == singleUser) {
- seenSingleUser = true;
+ if (inst == firstUser) {
+ seenFirstUser = true;
continue;
}
ArrayPut arrayPut = inst.asArrayPut();
if (arrayPut == null || arrayPut.array() != arrayValue) {
continue;
}
- if (seenSingleUser) {
+ if (seenFirstUser) {
// Found an array-put after the array was used. This is too uncommon of a thing to support.
return null;
}
@@ -197,10 +275,14 @@
if (index < 0) {
return null;
}
- // We can allow reassignment at this point since we are visiting in order.
+ // Do not allow re-assignment so that we can use arrayPutsByIndex to find all array-put
+ // instructions.
+ if (arrayPutsByIndex[index] != null) {
+ return null;
+ }
arrayPutsByIndex[index] = arrayPut;
}
- return new ArrayValues(arrayPutsByIndex);
+ return new ArrayValues(arrayValue, arrayPutsByIndex);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index cdfb34c..ed63185 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -145,8 +145,8 @@
return seen.contains(item);
}
- public void markAsSeen(T item) {
- seen.add(item);
+ public boolean markAsSeen(T item) {
+ return seen.add(item);
}
public void markAsSeen(Iterable<T> items) {
@@ -154,16 +154,23 @@
}
public T next() {
- assert hasNext();
return workingList.removeFirst();
}
+ public T removeLast() {
+ return workingList.removeLast();
+ }
+
public T removeSeen() {
T next = next();
seen.remove(next);
return next;
}
+ public void clearSeen() {
+ seen.clear();
+ }
+
public Set<T> getSeenSet() {
return SetUtils.unmodifiableForTesting(seen);
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
index cfbc759..5bb0780 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
@@ -81,7 +81,7 @@
DexType baseType =
rewrittenMethod.getHolderType().toBaseType(appViewWithClassHierarchy.dexItemFactory());
if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
- if (checkTypeReference(rewrittenMethod.getHolderType())) {
+ if (checkRewrittenTypeReference(rewrittenMethod.getHolderType())) {
return checkFoundPackagePrivateAccess();
}
MethodResolutionResult resolutionResult =
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index 3987216..994795d 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -8,11 +8,13 @@
import com.android.tools.r8.classmerging.ClassMergerGraphLens;
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.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.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
@@ -22,6 +24,9 @@
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.conversion.ExtraParameter;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
@@ -283,7 +288,10 @@
}
if (type.isInterface()
&& mergedClasses.hasInterfaceBeenMergedIntoClass(previousMethod.getHolderType())) {
- return InvokeType.VIRTUAL;
+ DexClass newMethodHolder = appView.definitionForHolder(newMethod);
+ if (newMethodHolder != null && !newMethodHolder.isInterface()) {
+ return InvokeType.VIRTUAL;
+ }
}
return type;
}
@@ -309,6 +317,29 @@
return true;
}
+ public boolean assertPinnedNotModified(AppView<AppInfoWithLiveness> appView) {
+ KeepInfoCollection keepInfo = appView.getKeepInfo();
+ InternalOptions options = appView.options();
+ keepInfo.forEachPinnedType(this::assertReferenceNotModified, options);
+ keepInfo.forEachPinnedMethod(this::assertReferenceNotModified, options);
+ keepInfo.forEachPinnedField(this::assertReferenceNotModified, options);
+ return true;
+ }
+
+ private void assertReferenceNotModified(DexReference reference) {
+ if (reference.isDexField()) {
+ DexField field = reference.asDexField();
+ assert getNextFieldSignature(field).isIdenticalTo(field);
+ } else if (reference.isDexMethod()) {
+ DexMethod method = reference.asDexMethod();
+ assert getNextMethodSignature(method).isIdenticalTo(method);
+ } else {
+ assert reference.isDexType();
+ DexType type = reference.asDexType();
+ assert getNextClassType(type).isIdenticalTo(type);
+ }
+ }
+
public static class Builder
extends BuilderBase<VerticalClassMergerGraphLens, VerticallyMergedClasses> {
@@ -469,13 +500,6 @@
staticizedMethods);
}
- // TODO: should be removed.
- public boolean hasMappingForSignatureInContext(DexProgramClass context, DexMethod signature) {
- return contextualSuperToImplementationInContexts
- .getOrDefault(context.getType(), Collections.emptyMap())
- .containsKey(signature);
- }
-
public void markMethodAsMerged(DexEncodedMethod method) {
mergedMethods.add(method.getReference());
}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 842b6a8..aaee1b0 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -329,15 +329,25 @@
assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
}
+ @Test
+ public void mainDexListNonLegacyMinApiL() throws Throwable {
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command =
+ parse(
+ "--min-api", Integer.toString(AndroidApiLevel.L.getLevel()),
+ "--main-dex-list", mainDexList.toString());
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
+ }
+
@Test(expected = CompilationFailedException.class)
- public void mainDexListWithNonLegacyMinApi() throws Throwable {
+ public void mainDexListWithNonLegacyMinApiAboveL() throws Throwable {
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
DiagnosticsChecker.checkErrorsContains(
"does not support main-dex",
(handler) ->
D8Command.builder(handler)
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setMinApiLevel(AndroidApiLevel.L.getLevel())
+ .setMinApiLevel(AndroidApiLevel.L_MR1.getLevel())
.addMainDexListFiles(mainDexList)
.build());
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInvokeVirtualToInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInvokeVirtualToInterfaceTest.java
new file mode 100644
index 0000000..2a8fa07
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInvokeVirtualToInterfaceTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2024, 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.vertical;
+
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+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 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 VerticalClassMergerInvokeVirtualToInterfaceTest extends TestBase {
+
+ @Parameter(0)
+ 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)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(J.class).assertNoOtherClassesMerged())
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoParameterTypeStrengtheningAnnotations()
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!", "Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ callOnI(new B());
+ callOnJ(new B());
+ }
+
+ @NeverInline
+ @NoParameterTypeStrengthening
+ static void callOnI(I i) {
+ i.m();
+ }
+
+ @NeverInline
+ @NoParameterTypeStrengthening
+ static void callOnJ(J j) {
+ j.m();
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {
+
+ void m();
+ }
+
+ @NoUnusedInterfaceRemoval
+ interface J {
+
+ void m();
+ }
+
+ @NoVerticalClassMerging
+ abstract static class A implements I, J {}
+
+ @NeverClassInline
+ static class B extends A {
+
+ @NeverInline
+ public void m() {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 128178a..ba3e8e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -16,6 +16,8 @@
import com.android.tools.r8.DexFilePerClassFileConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.SyntheticInfoConsumer;
+import com.android.tools.r8.SyntheticInfoConsumerData;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -23,7 +25,9 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.desugar.backports.AbstractBackportTest.MiniAssert;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -37,8 +41,10 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
@@ -342,6 +348,115 @@
assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
}
+ private static class ContextCollector implements SyntheticInfoConsumer {
+
+ Map<ClassReference, Set<ClassReference>> contextToSynthetics = new HashMap<>();
+
+ @Override
+ public synchronized void acceptSyntheticInfo(SyntheticInfoConsumerData data) {
+ contextToSynthetics
+ .computeIfAbsent(data.getSynthesizingContextClass(), k -> new HashSet<>())
+ .add(data.getSyntheticClass());
+ }
+
+ @Override
+ public void finished() {}
+ }
+
+ private static class PerFileCollector extends DexFilePerClassFileConsumer.ForwardingConsumer {
+
+ Map<ClassReference, byte[]> data = new HashMap<>();
+ ContextCollector contexts = new ContextCollector();
+
+ public PerFileCollector() {
+ super(null);
+ }
+
+ @Override
+ public boolean combineSyntheticClassesWithPrimaryClass() {
+ return false;
+ }
+
+ @Override
+ public synchronized void accept(
+ String primaryClassDescriptor,
+ ByteDataView data,
+ Set<String> descriptors,
+ DiagnosticsHandler handler) {
+ super.accept(primaryClassDescriptor, data, descriptors, handler);
+ this.data.put(Reference.classFromDescriptor(primaryClassDescriptor), data.copyByteData());
+ }
+ }
+
+ @Test
+ public void testDoubleCompileSyntheticInputsD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ // This is a regression test for the pathological case of recompiling intermediates in
+ // intermediate mode, but where the second round of compilation can share more than what was
+ // originally shared. Such a case should never be hit by any reasonable compilation pipeline,
+ // but it could by chance happen if a bytecode transformation was between the two intermediate
+ // steps that ended up making two previously distinct synthetics equivalent. To simulate such
+ // a case the sharing of synthetics is internally disabled so that we know the second round
+ // would see equivalent synthetics.
+
+ // Compile part 1 of the input with sharing completely disabled.
+ PerFileCollector out1 = new PerFileCollector();
+ testForD8(parameters.getBackend())
+ .addOptionsModification(o -> o.testing.enableSyntheticSharing = false)
+ .addProgramClasses(User1.class)
+ .addClasspathClasses(CLASSES)
+ .setMinApi(parameters)
+ .setIntermediate(true)
+ .setProgramConsumer(out1)
+ .apply(b -> b.getBuilder().setSyntheticInfoConsumer(out1.contexts))
+ .compile();
+ // The total number of outputs is 8 of which 7 are synthetics in User1.
+ ClassReference user1 = Reference.classFromClass(User1.class);
+ assertEquals(8, out1.data.size());
+ assertEquals(1, out1.contexts.contextToSynthetics.size());
+ assertEquals(7, out1.contexts.contextToSynthetics.get(user1).size());
+
+ // Recompile a "shard" containing all the synthetics, but not the context.
+ PerFileCollector out2 = new PerFileCollector();
+ testForD8(parameters.getBackend())
+ .apply(
+ b ->
+ out1.contexts
+ .contextToSynthetics
+ .get(user1)
+ .forEach(synthetic -> b.addProgramDexFileData(out1.data.get(synthetic))))
+ .addClasspathClasses(CLASSES)
+ .setMinApi(parameters)
+ .setIntermediate(true)
+ .setProgramConsumer(out2)
+ .apply(b -> b.getBuilder().setSyntheticInfoConsumer(out2.contexts))
+ .compile();
+ // Again the total number of synthetics should remain 7 with no sharing taking place.
+ assertEquals(7, out2.data.size());
+
+ // Compile the remaining program inputs not compiled in the above.
+ // Note: the order of final synthetics depends on compiling to intermediates before merge.
+ Path out3 =
+ testForD8(parameters.getBackend())
+ .addProgramClasses(MiniAssert.class, TestClass.class, User2.class)
+ .setMinApi(parameters)
+ .setIntermediate(true)
+ .compile()
+ .writeToZip();
+
+ // Merge all into a final build.
+ testForD8(parameters.getBackend())
+ .addProgramDexFileData(out1.data.get(user1))
+ .addProgramDexFileData(out2.data.values())
+ .addProgramFiles(out3)
+ .setMinApi(parameters)
+ .setIntermediate(false)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+ .inspect(this::checkExpectedSynthetics);
+ }
+
static class User1 {
private static void testBooleanCompare() {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java
index f7c850a..c23da1d 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMappingTest.java
@@ -18,22 +18,20 @@
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 EnumUnboxingMappingTest extends EnumUnboxingTestBase {
- private final TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
- public EnumUnboxingMappingTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
@Test
public void testEnumUnboxing() throws Exception {
testForR8(parameters.getBackend())
@@ -64,14 +62,14 @@
assertEquals("int", debugInfoMethod.getFinalSignature().asMethodSignature().parameters[0]);
assertEquals("int", noDebugInfoMethod.getFinalSignature().asMethodSignature().parameters[0]);
- assertEquals(MyEnum.class.getName(), debugInfoMethod.getOriginalSignature().parameters[0]);
- // TODO(b/314076309): The original parameter should be MyEnum.class but is int.
- assertEquals("int", noDebugInfoMethod.getOriginalSignature().parameters[0]);
+ assertEquals(MyEnum.class.getTypeName(), debugInfoMethod.getOriginalSignature().parameters[0]);
+ assertEquals(
+ MyEnum.class.getTypeName(), noDebugInfoMethod.getOriginalSignature().parameters[0]);
ClassSubject indirection = codeInspector.clazz(Indirection.class);
MethodSubject abstractMethod = indirection.uniqueMethodWithOriginalName("intermediate");
assertTrue(abstractMethod.isAbstract());
- assertEquals(MyEnum.class.getName(), abstractMethod.getOriginalSignature().parameters[0]);
+ assertEquals(MyEnum.class.getTypeName(), abstractMethod.getOriginalSignature().parameters[0]);
}
@NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java b/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java
index f25b76b..6551be6 100644
--- a/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java
+++ b/src/test/java/com/android/tools/r8/examples/filledarray/FilledArray.java
@@ -115,8 +115,7 @@
}
try {
- // Array creation that cannot be turned into fill-array-data because an exception would
- // cause the initialization sequence to be interrupted.
+ // Exception does not prevent fill-array-data since only usage is within the try.
int[] ints = new int[5];
ints[0] = 0;
ints[1] = 1;
diff --git a/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternAnyRetentionTest.java b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternAnyRetentionTest.java
new file mode 100644
index 0000000..4b11ba6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternAnyRetentionTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+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 com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.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;
+
+@RunWith(Parameterized.class)
+public class AnnotationPatternAnyRetentionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1: A1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public AnnotationPatternAnyRetentionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, Reflector.class, A1.class, A2.class, C1.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // The class retention annotation is used and kept. It can be renamed as nothing prohibits that.
+ assertThat(inspector.clazz(A1.class), isPresentAndRenamed());
+ assertThat(inspector.clazz(A2.class), isPresentAndRenamed());
+ // The class is retained by the keep-annotation.
+ ClassSubject clazz = inspector.clazz(C1.class);
+ assertThat(clazz, isPresentAndNotRenamed());
+ assertThat(clazz.annotation(A1.class), isPresent());
+ assertThat(clazz.annotation(A2.class), isPresent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A2 {}
+
+ static class Reflector {
+
+ @UsesReflection({
+ @KeepTarget(
+ classAnnotatedByClassConstant = A2.class,
+ constrainAnnotations =
+ @AnnotationPattern(retention = {RetentionPolicy.CLASS, RetentionPolicy.RUNTIME})),
+ })
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ if (clazz.isAnnotationPresent(A1.class)) {
+ System.out.print(" A1");
+ }
+ // The below code will not trigger as A2 is not visible at runtime, but it will ensure the
+ // annotation is used.
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ @A1
+ @A2
+ static class C1 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternClassRetentionTest.java b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternClassRetentionTest.java
new file mode 100644
index 0000000..efc77cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternClassRetentionTest.java
@@ -0,0 +1,127 @@
+// Copyright (c) 2024, 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.keepanno;
+
+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.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+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 com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.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;
+
+@RunWith(Parameterized.class)
+public class AnnotationPatternClassRetentionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1:");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public AnnotationPatternClassRetentionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, Reflector.class, A1.class, A2.class, C1.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // Nothing is keeping A1 and no references exist to it.
+ // Having the constraint on annotations with class retention should not prohibit removal.
+ assertThat(inspector.clazz(A1.class), isAbsent());
+ // The class retention annotation is used and kept. It can be renamed as nothing prohibits that.
+ assertThat(inspector.clazz(A2.class), isPresentAndRenamed());
+ // The class is retained by the keep-annotation.
+ ClassSubject clazz = inspector.clazz(C1.class);
+ assertThat(clazz, isPresentAndNotRenamed());
+ assertThat(clazz.annotation(A1.class), isAbsent());
+ assertThat(clazz.annotation(A2.class), isPresent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A2 {}
+
+ static class Reflector {
+
+ @UsesReflection({
+ @KeepTarget(
+ classAnnotatedByClassConstant = A2.class,
+ constrainAnnotations = @AnnotationPattern(retention = RetentionPolicy.CLASS)),
+ })
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ // Ignoring A1 as we explicitly have no keep-annotation to preserve it.
+ // The below code will not trigger as A2 is not visible at runtime, but it will ensure the
+ // annotation is used.
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ @A1
+ @A2
+ static class C1 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternMultipleTest.java b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternMultipleTest.java
new file mode 100644
index 0000000..33a4f6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/AnnotationPatternMultipleTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2024, 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.keepanno;
+
+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.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+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 com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.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;
+
+@RunWith(Parameterized.class)
+public class AnnotationPatternMultipleTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1: A1", "C2:");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public AnnotationPatternMultipleTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(
+ TestClass.class, Reflector.class, A1.class, A2.class, A3.class, C1.class, C2.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A1.class), isPresentAndRenamed());
+ assertThat(inspector.clazz(A2.class), isPresentAndRenamed());
+ // No use of A3 so R8 will remove it.
+ assertThat(inspector.clazz(A3.class), isAbsent());
+ // The class is retained by the keep-annotation.
+ ClassSubject c1 = inspector.clazz(C1.class);
+ assertThat(c1, isPresentAndNotRenamed());
+ assertThat(c1.annotation(A1.class), isPresent());
+ assertThat(c1.annotation(A3.class), isAbsent());
+
+ ClassSubject c2 = inspector.clazz(C2.class);
+ assertThat(c2, isPresentAndNotRenamed());
+ assertThat(c2.annotation(A2.class), isPresent());
+ assertThat(c2.annotation(A3.class), isAbsent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A2 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A3 {}
+
+ static class Reflector {
+
+ @UsesReflection(
+ @KeepTarget(
+ classAnnotatedByClassNamePattern =
+ @ClassNamePattern(packageName = "com.android.tools.r8.keepanno"),
+ constrainAnnotations = {
+ @AnnotationPattern(constant = A1.class, retention = RetentionPolicy.RUNTIME),
+ @AnnotationPattern(constant = A2.class, retention = RetentionPolicy.CLASS)
+ }))
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ if (clazz.isAnnotationPresent(A1.class)) {
+ System.out.print(" A1");
+ }
+ // The below code will not trigger as A2 is not visible at runtime, but it will ensure the
+ // annotation is used.
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ System.out.println();
+ }
+ }
+ }
+
+ @A1
+ @A3
+ static class C1 {}
+
+ @A2
+ @A3
+ static class C2 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class, C2.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoConstraintPatternTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoConstraintPatternTest.java
new file mode 100644
index 0000000..0a33131
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoConstraintPatternTest.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+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 com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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;
+
+@RunWith(Parameterized.class)
+public class ClassAnnotatedByAnyAnnoConstraintPatternTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("C1: A1", "C2: A2");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ClassAnnotatedByAnyAnnoConstraintPatternTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(
+ TestClass.class,
+ Reflector.class,
+ A1.class,
+ A2.class,
+ A3.class,
+ C1.class,
+ C2.class,
+ C3.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ // The class constant use will ensure the annotations remains.
+ // They are not renamed as the keep-annotation will match them (they are themselves annotated).
+ assertThat(inspector.clazz(A1.class), isPresentAndNotRenamed());
+ assertThat(inspector.clazz(A2.class), isPresentAndNotRenamed());
+ assertThat(inspector.clazz(A3.class), isPresentAndNotRenamed());
+ // The first two classes are annotated so the keep-annotation applies and retains their name.
+ assertThat(inspector.clazz(C1.class), isPresentAndNotRenamed());
+ assertThat(inspector.clazz(C2.class), isPresentAndNotRenamed());
+ // The last class will remain due to the class constant, but it is optimized/renamed.
+ assertThat(inspector.clazz(C3.class), isPresentAndRenamed());
+ // The class-retention annotation should not be present on the class as the pattern defaults
+ // to only match runtime retention classes.
+ assertThat(inspector.clazz(C2.class).annotation(A3.class), isAbsent());
+ }
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A1 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface A2 {}
+
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.CLASS)
+ @interface A3 {}
+
+ static class Reflector {
+
+ @UsesReflection({
+ @KeepTarget(
+ classAnnotatedByClassNamePattern = @ClassNamePattern,
+ constraints = {KeepConstraint.NAME},
+ constrainAnnotations = @AnnotationPattern),
+ })
+ public void foo(Class<?>... classes) throws Exception {
+ for (Class<?> clazz : classes) {
+ if (clazz.getAnnotations().length > 0) {
+ String typeName = clazz.getTypeName();
+ System.out.print(typeName.substring(typeName.lastIndexOf('$') + 1) + ":");
+ if (clazz.isAnnotationPresent(A1.class)) {
+ System.out.print(" A1");
+ }
+ if (clazz.isAnnotationPresent(A2.class)) {
+ System.out.print(" A2");
+ }
+ if (clazz.isAnnotationPresent(A3.class)) {
+ System.out.print(" A3");
+ }
+ System.out.println();
+ }
+ }
+ }
+ }
+
+ @A1
+ static class C1 {}
+
+ @A2
+ @A3
+ static class C2 {}
+
+ static class C3 {}
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new Reflector().foo(C1.class, C2.class, C3.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoPatternTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoPatternTest.java
index 684a4e2..6e8ea51 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoPatternTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByAnyAnnoPatternTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
import com.android.tools.r8.keepanno.annotations.KeepConstraint;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
@@ -91,11 +92,14 @@
static class Reflector {
- @UsesReflection({
- @KeepTarget(
- classAnnotatedByClassNamePattern = @ClassNamePattern,
- constraints = {KeepConstraint.ANNOTATIONS, KeepConstraint.NAME}),
- })
+ @UsesReflection(
+ @KeepTarget(
+ classAnnotatedByClassNamePattern = @ClassNamePattern,
+ constraints = KeepConstraint.NAME,
+ constrainAnnotations = {
+ @AnnotationPattern(constant = A1.class),
+ @AnnotationPattern(constant = A2.class)
+ }))
public void foo(Class<?>... classes) throws Exception {
for (Class<?> clazz : classes) {
if (clazz.getAnnotations().length > 0) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java
index 6d7e6c8..d828d7f 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ClassAnnotatedByPatternsTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.KeepConstraint;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
@@ -95,7 +96,8 @@
@UsesReflection(
@KeepTarget(
classAnnotatedByClassConstant = A1.class,
- constraints = {KeepConstraint.ANNOTATIONS, KeepConstraint.NAME}))
+ constraints = KeepConstraint.NAME,
+ constrainAnnotations = @AnnotationPattern(constant = A1.class)))
public void foo(Class<?>... classes) throws Exception {
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(A1.class)) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
index dda99e1..6b4bfac 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnotationViaSuperTest.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -106,7 +106,8 @@
@UsesReflection({
@KeepTarget(
instanceOfClassConstantExclusive = Base.class,
- constraints = {KeepConstraint.ANNOTATIONS})
+ constraints = {},
+ constrainAnnotations = @AnnotationPattern(constant = Anno.class))
})
public Base() {
Anno annotation = getClass().getAnnotation(Anno.class);
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
index 0eace48..d8d5941 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.KeepConstraint;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.StringPattern;
@@ -186,6 +187,48 @@
}
}
+ @Test
+ public void testEmptyAnnotationConstraints() {
+ assertThrowsWith(
+ () -> extractRuleForClass(EmptyAnnotationConstraints.class),
+ allOf(
+ containsString("Expected non-empty array"),
+ containsString("at property: constrainAnnotations")));
+ }
+
+ static class EmptyAnnotationConstraints {
+
+ @UsesReflection(
+ @KeepTarget(
+ classConstant = A.class,
+ constrainAnnotations = {}))
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+
+ @Test
+ public void testEmptyAnnotationPatternRetention() {
+ assertThrowsWith(
+ () -> extractRuleForClass(EmptyAnnotationPatternRetention.class),
+ allOf(
+ containsString("Expected non-empty array"),
+ containsString("at property: retention"),
+ containsString("at annotation: @AnnotationPattern"),
+ containsString("at property: constrainAnnotations")));
+ }
+
+ static class EmptyAnnotationPatternRetention {
+
+ @UsesReflection(
+ @KeepTarget(
+ classConstant = A.class,
+ constrainAnnotations = @AnnotationPattern(retention = {})))
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+
static class A {
// just a target.
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
index 19e2290..7c57ea6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsedByNativeAnnotationTest.java
@@ -10,8 +10,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.KeepCondition;
-import com.android.tools.r8.keepanno.annotations.KeepConstraint;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.UsedByNative;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -108,7 +108,7 @@
preconditions = {@KeepCondition(classConstant = A.class, methodName = "foo")},
// Both the class and method are reflectively accessed.
kind = KeepItemKind.CLASS_AND_MEMBERS,
- constraintAdditions = {KeepConstraint.ANNOTATIONS})
+ constrainAnnotations = @AnnotationPattern(constant = Anno.class))
@Anno("anno-on-bar")
public static void bar() {
System.out.println("Hello, world");
diff --git a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
index 642e269..0f2aadf 100644
--- a/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/MembersAnnotatedByPatternsTest.java
@@ -11,8 +11,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
-import com.android.tools.r8.keepanno.annotations.KeepConstraint;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.UsedByReflection;
@@ -29,6 +29,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
+import org.checkerframework.checker.units.qual.A;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -122,19 +123,23 @@
classConstant = OnMembers.class,
kind = KeepItemKind.CLASS_AND_MEMBERS,
memberAnnotatedByClassConstant = A.class,
- constraintAdditions = {KeepConstraint.ANNOTATIONS}),
+ constrainAnnotations = @AnnotationPattern(constant = A.class)),
@KeepTarget(
classConstant = OnFields.class,
kind = KeepItemKind.CLASS_AND_FIELDS,
fieldAnnotatedByClassName =
"com.android.tools.r8.keepanno.MembersAnnotatedByPatternsTest$B",
- constraintAdditions = {KeepConstraint.ANNOTATIONS}),
+ constrainAnnotations =
+ @AnnotationPattern(
+ name = "com.android.tools.r8.keepanno.MembersAnnotatedByPatternsTest$B")),
@KeepTarget(
classConstant = OnMethods.class,
kind = KeepItemKind.CLASS_AND_METHODS,
methodAnnotatedByClassNamePattern =
@ClassNamePattern(simpleName = "MembersAnnotatedByPatternsTest$C"),
- constraintAdditions = {KeepConstraint.ANNOTATIONS})
+ constrainAnnotations =
+ @AnnotationPattern(
+ namePattern = @ClassNamePattern(simpleName = "MembersAnnotatedByPatternsTest$C")))
})
public void foo(Class<?> clazz) throws Exception {
for (Field field : clazz.getDeclaredFields()) {
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionAnnotationsDocumentationTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionAnnotationsDocumentationTest.java
new file mode 100644
index 0000000..a694a82
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/UsesReflectionAnnotationsDocumentationTest.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2024, 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.keepanno.doctests;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+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 com.android.tools.r8.keepanno.annotations.AnnotationPattern;
+import com.android.tools.r8.keepanno.annotations.KeepConstraint;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.doctests.UsesReflectionAnnotationsDocumentationTest.Example1.MyAnnotationPrinter.MyClass;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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.lang.reflect.Field;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UsesReflectionAnnotationsDocumentationTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("fieldOne = 1", "fieldTwo = 2");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public UsesReflectionAnnotationsDocumentationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(TestClass.class)
+ .addProgramClassesAndInnerClasses(getExampleClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(TestClass.class)
+ .addProgramClassesAndInnerClasses(getExampleClasses())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ ClassSubject clazz = inspector.clazz(MyClass.class);
+ assertThat(clazz.uniqueFieldWithOriginalName("mFieldOne"), isPresentAndRenamed());
+ assertThat(clazz.uniqueFieldWithOriginalName("mFieldTwo"), isPresentAndRenamed());
+ assertThat(clazz.uniqueFieldWithOriginalName("mFieldThree"), isAbsent());
+ });
+ }
+
+ public List<Class<?>> getExampleClasses() {
+ return ImmutableList.of(Example1.class);
+ }
+
+ static class Example1 {
+
+ /* INCLUDE DOC: UsesReflectionOnAnnotations
+ If your program is reflectively inspecting annotations on classes, methods or fields, you
+ will need to declare additional "annotation constraints" about what assumptions are made
+ about the annotations.
+
+ In the following example, we have defined an annotation that will record the printing name we
+ would like to use for fields instead of printing the concrete field name. That may be useful
+ so that the field can be renamed to follow coding conventions for example.
+
+ We are only interested in matching objects that contain fields annotated by `MyNameAnnotation`,
+ that is specified using `@KeepTarget#fieldAnnotatedByClassConstant`.
+
+ At runtime we need to be able to find the annotation too, so we add a constraint on the
+ annotation using `@KeepTarget#constrainAnnotations`.
+
+ Finally, for the sake of example, we don't actually care about the name of the fields
+ themselves, so we explicitly declare the smaller set of constraints to be
+ `@KeepConstraint#LOOKUP` since we must find the fields via `Class.getDeclaredFields` as well as
+ `@KeepConstraint#VISIBILITY_RELAX` and `@KeepConstraint#FIELD_GET` in order to be able to get
+ the actual field value without accessibility errors.
+
+ The effect is that the default constraint `@KeepConstraint#NAME` is not specified which allows
+ the shrinker to rename the fields at will.
+ INCLUDE END */
+
+ static
+ // INCLUDE CODE: UsesReflectionOnAnnotations
+ public class MyAnnotationPrinter {
+
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface MyNameAnnotation {
+ String value();
+ }
+
+ public static class MyClass {
+ @MyNameAnnotation("fieldOne")
+ public int mFieldOne = 1;
+
+ @MyNameAnnotation("fieldTwo")
+ public int mFieldTwo = 2;
+
+ public int mFieldThree = 3;
+ }
+
+ @UsesReflection(
+ @KeepTarget(
+ fieldAnnotatedByClassConstant = MyNameAnnotation.class,
+ constrainAnnotations = @AnnotationPattern(constant = MyNameAnnotation.class),
+ constraints = {
+ KeepConstraint.LOOKUP,
+ KeepConstraint.VISIBILITY_RELAX,
+ KeepConstraint.FIELD_GET
+ }))
+ public void printMyNameAnnotatedFields(Object obj) throws Exception {
+ for (Field field : obj.getClass().getDeclaredFields()) {
+ if (field.isAnnotationPresent(MyNameAnnotation.class)) {
+ System.out.println(
+ field.getAnnotation(MyNameAnnotation.class).value() + " = " + field.get(obj));
+ }
+ }
+ }
+ }
+
+ // INCLUDE END
+
+ static void run() throws Exception {
+ new MyAnnotationPrinter().printMyNameAnnotatedFields(new MyAnnotationPrinter.MyClass());
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ Example1.run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
index 9376fa3..c2dec45 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepAnnoMarkdownGenerator.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.quote;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
import com.android.tools.r8.keepanno.annotations.KeepBinding;
import com.android.tools.r8.keepanno.annotations.KeepCondition;
@@ -17,11 +18,14 @@
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.MemberAccessFlags;
import com.android.tools.r8.keepanno.annotations.MethodAccessFlags;
+import com.android.tools.r8.keepanno.annotations.StringPattern;
+import com.android.tools.r8.keepanno.annotations.TypePattern;
import com.android.tools.r8.keepanno.annotations.UsedByNative;
import com.android.tools.r8.keepanno.annotations.UsedByReflection;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
import com.android.tools.r8.keepanno.doctests.ForApiDocumentationTest;
import com.android.tools.r8.keepanno.doctests.MainMethodsDocumentationTest;
+import com.android.tools.r8.keepanno.doctests.UsesReflectionAnnotationsDocumentationTest;
import com.android.tools.r8.keepanno.doctests.UsesReflectionDocumentationTest;
import com.android.tools.r8.keepanno.utils.KeepItemAnnotationGenerator.Generator;
import com.android.tools.r8.utils.FileUtils;
@@ -86,6 +90,9 @@
UsedByReflection.class,
UsedByNative.class,
KeepForApi.class,
+ StringPattern.class,
+ TypePattern.class,
+ AnnotationPattern.class,
// Enums.
KeepConstraint.class,
KeepItemKind.class,
@@ -94,6 +101,7 @@
FieldAccessFlags.class);
populateCodeAndDocReplacements(
UsesReflectionDocumentationTest.class,
+ UsesReflectionAnnotationsDocumentationTest.class,
ForApiDocumentationTest.class,
MainMethodsDocumentationTest.class);
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index 0852bde..475813b 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cfmethodgeneration.CodeGenerationBase;
+import com.android.tools.r8.keepanno.annotations.AnnotationPattern;
import com.android.tools.r8.keepanno.annotations.CheckOptimizedOut;
import com.android.tools.r8.keepanno.annotations.CheckRemoved;
import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
@@ -301,6 +302,7 @@
private static String MEMBER_ANNOTATED_BY_GROUP = "member-annotated-by";
private static String METHOD_ANNOTATED_BY_GROUP = "method-annotated-by";
private static String FIELD_ANNOTATED_BY_GROUP = "field-annotated-by";
+ private static String ANNOTATION_NAME_GROUP = "annotation-name";
private Group createDescriptionGroup() {
return new Group("description")
@@ -487,12 +489,13 @@
docLink(KeepItemKind.ONLY_MEMBERS) + " otherwise.");
}
- private Group getKeepConstraintsGroup() {
- return new Group(CONSTRAINTS_GROUP).addMember(constraints()).addMember(constraintAdditions());
+ private void forEachKeepConstraintGroups(Consumer<Group> fn) {
+ fn.accept(getKeepConstraintsGroup());
+ fn.accept(new Group("constrain-annotations").addMember(constrainAnnotations()));
}
- private static String docLinkList(Enum<?>... values) {
- return StringUtils.join(", ", values, v -> docLink(v), BraceType.TUBORG);
+ private Group getKeepConstraintsGroup() {
+ return new Group(CONSTRAINTS_GROUP).addMember(constraints()).addMember(constraintAdditions());
}
private static GroupMember constraints() {
@@ -527,6 +530,69 @@
.defaultArrayEmpty(KeepConstraint.class);
}
+ private static GroupMember constrainAnnotations() {
+ return new GroupMember("constrainAnnotations")
+ .setDocTitle("Patterns for annotations that must remain on the item.")
+ .addParagraph(
+ "The annotations matching any of the patterns must remain on the item",
+ "if the annotation types remain in the program.")
+ .addParagraph(
+ "Note that if the annotation types themselves are unused/removed,",
+ "then their references on the item will be removed too.",
+ "If the annotation types themselves are used reflectively then they too need a",
+ "keep annotation or rule to ensure they remain in the program.")
+ .addParagraph(
+ "By default no annotation patterns are defined and no annotations are required to",
+ "remain.")
+ .setDocReturn("Annotation patterns")
+ .defaultArrayEmpty(AnnotationPattern.class);
+ }
+
+ private Group annotationNameGroup() {
+ return new Group(ANNOTATION_NAME_GROUP)
+ .addMember(annotationName())
+ .addMember(annotationConstant())
+ .addMember(annotationNamePattern())
+ .addDocFooterParagraph(
+ "If none are specified the default is to match any annotation name.");
+ }
+
+ private GroupMember annotationName() {
+ return new GroupMember("name")
+ .setDocTitle(
+ "Define the " + ANNOTATION_NAME_GROUP + " pattern by fully qualified class name.")
+ .setDocReturn("The qualified class name that defines the annotation.")
+ .defaultEmptyString();
+ }
+
+ private GroupMember annotationConstant() {
+ return new GroupMember("constant")
+ .setDocTitle(
+ "Define the "
+ + ANNOTATION_NAME_GROUP
+ + " pattern by reference to a {@code Class} constant.")
+ .setDocReturn("The Class constant that defines the annotation.")
+ .defaultObjectClass();
+ }
+
+ private GroupMember annotationNamePattern() {
+ return new GroupMember("namePattern")
+ .setDocTitle(
+ "Define the "
+ + ANNOTATION_NAME_GROUP
+ + " pattern by reference to a class-name pattern.")
+ .setDocReturn("The class-name pattern that defines the annotation.")
+ .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN);
+ }
+
+ private static GroupMember annotationRetention() {
+ return new GroupMember("retention")
+ .setDocTitle("Specify which retention policies must be set for the annotations.")
+ .addParagraph("Matches annotations with matching retention policies")
+ .setDocReturn("Retention policies. By default {@code RetentionPolicy.RUNTIME}.")
+ .defaultArrayValue(RetentionPolicy.class, "RetentionPolicy.RUNTIME");
+ }
+
private GroupMember bindingName() {
return new GroupMember("bindingName")
.setDocTitle(
@@ -1024,6 +1090,30 @@
println("}");
}
+ private void generateAnnotationPattern() {
+ printCopyRight(2024);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A pattern structure for matching annotations.")
+ .addParagraph(
+ "If no properties are set, the default pattern matches any annotation",
+ "with a runtime retention policy.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface " + simpleName(AnnotationPattern.class) + " {");
+ println();
+ withIndent(
+ () -> {
+ annotationNameGroup().generate(this);
+ println();
+ annotationRetention().generate(this);
+ });
+ println();
+ println("}");
+ }
+
private void generateKeepBinding() {
printCopyRight(2022);
printPackage("annotations");
@@ -1073,8 +1163,11 @@
() -> {
getKindGroup().generate(this);
println();
- getKeepConstraintsGroup().generate(this);
- println();
+ forEachKeepConstraintGroups(
+ g -> {
+ g.generate(this);
+ println();
+ });
generateClassAndMemberPropertiesWithClassAndMemberBinding();
});
println();
@@ -1294,8 +1387,11 @@
+ " if annotating a member.")
.generate(this);
println();
- getKeepConstraintsGroup().generate(this);
- println();
+ forEachKeepConstraintGroups(
+ g -> {
+ g.generate(this);
+ println();
+ });
generateMemberPropertiesNoBinding();
});
println();
@@ -1318,6 +1414,10 @@
return "{@link " + simpleName(kind.getClass()) + "#" + kind.name() + "}";
}
+ private static String docLinkList(Enum<?>... values) {
+ return StringUtils.join(", ", values, v -> docLink(v), BraceType.TUBORG);
+ }
+
private void generateConstants() {
printCopyRight(2023);
printPackage("ast");
@@ -1356,6 +1456,7 @@
generateStringPatternConstants();
generateTypePatternConstants();
generateClassNamePatternConstants();
+ generateAnnotationPatternConstants();
});
println("}");
}
@@ -1509,7 +1610,7 @@
() -> {
generateAnnotationConstants(KeepTarget.class);
getKindGroup().generateConstants(this);
- getKeepConstraintsGroup().generateConstants(this);
+ forEachKeepConstraintGroups(g -> g.generateConstants(this));
});
println("}");
println();
@@ -1663,6 +1764,18 @@
println();
}
+ private void generateAnnotationPatternConstants() {
+ println("public static final class AnnotationPattern {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(AnnotationPattern.class);
+ annotationNameGroup().generateConstants(this);
+ annotationRetention().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
private static void writeFile(Path file, Consumer<Generator> fn) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteStream);
@@ -1691,6 +1804,7 @@
writeFile(source(annoPkg, StringPattern.class), Generator::generateStringPattern);
writeFile(source(annoPkg, TypePattern.class), Generator::generateTypePattern);
writeFile(source(annoPkg, ClassNamePattern.class), Generator::generateClassNamePattern);
+ writeFile(source(annoPkg, AnnotationPattern.class), Generator::generateAnnotationPattern);
writeFile(source(annoPkg, KeepBinding.class), Generator::generateKeepBinding);
writeFile(source(annoPkg, KeepTarget.class), Generator::generateKeepTarget);
writeFile(source(annoPkg, KeepCondition.class), Generator::generateKeepCondition);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
new file mode 100644
index 0000000..b9c3c87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexApi21Test.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2024, 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test for allowing main-dex support for API 21 / L (See b//320893283).
+@RunWith(Parameterized.class)
+public class MainDexApi21Test extends TestBase {
+
+ static class TestClassA {}
+
+ static class TestClassB {}
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public MainDexApi21Test(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ static class TestConsumer extends DexIndexedConsumer.ForwardingConsumer {
+
+ Map<Integer, byte[]> data = new HashMap<>();
+ Map<Integer, Set<String>> descriptors = new HashMap<>();
+
+ public TestConsumer() {
+ super(null);
+ }
+
+ @Override
+ public synchronized void accept(
+ int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+ assertNull(this.data.put(fileIndex, data.copyByteData()));
+ assertNull(this.descriptors.put(fileIndex, descriptors));
+ }
+ }
+
+ @Test
+ public void testWithoutMainDexInputs() throws Exception {
+ // Test to ensure that running at API 21 / L without any main-dex content works.
+ // Since L does support native multdex the compiler should not require any main-dex inputs.
+ TestConsumer programConsumer = new TestConsumer();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .setProgramConsumer(programConsumer)
+ .compile();
+ assertEquals(1, programConsumer.data.size());
+ assertEquals(1, programConsumer.descriptors.size());
+ assertEquals(
+ ImmutableSet.of(descriptor(TestClassA.class), descriptor(TestClassB.class)),
+ programConsumer.descriptors.get(0));
+ }
+
+ @Test
+ public void testWithMainDexRules() throws Exception {
+ // Test to ensure that running at API 21 / L with main-dex rules will actually produce a
+ // main-dex file. Debug mode will compile to a minimal main-dex, so we will observe two files.
+ TestConsumer programConsumer = new TestConsumer();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .addMainDexKeepClassAndMemberRules(TestClassB.class)
+ .setProgramConsumer(programConsumer)
+ .compile();
+ assertEquals(2, programConsumer.data.size());
+ assertEquals(2, programConsumer.descriptors.size());
+ assertEquals(ImmutableSet.of(descriptor(TestClassB.class)), programConsumer.descriptors.get(0));
+ assertEquals(ImmutableSet.of(descriptor(TestClassA.class)), programConsumer.descriptors.get(1));
+ }
+
+ @Test
+ public void testWithMainDexList() throws Exception {
+ // Test to ensure that running at API 21 / L with main-dex list will actually produce a
+ // main-dex file. Debug mode will compile to a minimal main-dex, so we will observe two files.
+ TestConsumer programConsumer = new TestConsumer();
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(ImmutableList.of(TestClassA.class, TestClassB.class))
+ .addMainDexListClasses(TestClassB.class)
+ .setProgramConsumer(programConsumer)
+ .compile();
+ assertEquals(2, programConsumer.data.size());
+ assertEquals(2, programConsumer.descriptors.size());
+ assertEquals(ImmutableSet.of(descriptor(TestClassB.class)), programConsumer.descriptors.get(0));
+ assertEquals(ImmutableSet.of(descriptor(TestClassA.class)), programConsumer.descriptors.get(1));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java
index 82ae38f..26a4674 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfArraysTest.java
@@ -63,13 +63,16 @@
}
private void inspect(CodeInspector inspector) {
+ int canUseStrings = canUseFilledNewArrayOfStringObjects(parameters) ? 1 : 0;
+ int canUseObjects = canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0;
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- 2
- + (canUseFilledNewArrayOfStringObjects(parameters) ? 2 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 2 : 0),
+ 2 + 2 * canUseStrings + 2 * canUseObjects,
2);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 2, 2);
+ inspect(
+ inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
+ 2 + 2 * canUseStrings + 1 * canUseObjects,
+ compilationMode.isDebug() ? 10 : 11);
}
@Test
@@ -129,32 +132,32 @@
@NeverInline
public static void m2() {
+ Object[] array = null;
try {
- Object[] array = {
- new byte[] {(byte) 1},
- new short[] {(short) 1},
- new int[] {1},
- new long[] {1L},
- new char[] {(char) 1},
- new float[] {1.0f},
- new double[] {1.0d},
- new String[] {"one"},
- new Object[] {
- new byte[] {(byte) 2},
- new short[] {(short) 2},
- new int[] {2},
- new long[] {2L},
- new char[] {(char) 2},
- new float[] {2.0f},
- new double[] {2.0d},
- new String[] {"two"},
- }
- };
- printArray(array);
- System.out.println();
+ array = new Object[9];
+ array[0] = new byte[] {(byte) 1};
+ array[1] = new short[] {(short) 1};
+ array[2] = new int[] {1};
+ array[3] = new long[] {1L};
+ array[4] = new char[] {(char) 1};
+ array[5] = new float[] {1.0f};
+ array[6] = new double[] {1.0d};
+ array[7] = new String[] {"one"};
+ array[8] =
+ new Object[] {
+ new byte[] {(byte) 2},
+ new short[] {(short) 2},
+ new int[] {2},
+ new long[] {2L},
+ new char[] {(char) 2},
+ new float[] {2.0f},
+ new double[] {2.0d},
+ new String[] {"two"},
+ };
} catch (Exception e) {
- throw new RuntimeException();
}
+ printArray(array);
+ System.out.println();
}
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java
index 63fec32..b37c8a2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfConstClassArraysTest.java
@@ -62,10 +62,7 @@
private void inspect(CodeInspector inspector) {
inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, 1);
- inspect(
- inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- 0,
- compilationMode.isDebug() ? 1 : 2);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, 4);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java
index edc54de..c429641 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfIntArraysTest.java
@@ -59,15 +59,15 @@
private void inspect(CodeInspector inspector) {
// This test use smaller int arrays, where filled-new-array is preferred over filled-array-data.
+ int canUseObjects = canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0;
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- 4 + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0),
+ 4 + 1 * canUseObjects,
1);
- // With catch handler the int[][] creation is not converted to filled-new-array.
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- 4,
- compilationMode.isDebug() ? 1 : 2);
+ 4 + 1 * canUseObjects,
+ 4);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java
index 1934f24..d238b1a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayOfStringArraysTest.java
@@ -63,28 +63,27 @@
}
private void inspect(CodeInspector inspector) {
+ int canUseStrings = canUseFilledNewArrayOfStringObjects(parameters) ? 1 : 0;
+ int canUseObjects = canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0;
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- (canUseFilledNewArrayOfStringObjects(parameters) ? 4 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0),
+ 4 * canUseStrings + 1 * canUseObjects,
1);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- 0,
- compilationMode.isDebug() ? 2 : 1);
+ 4 * canUseStrings + 1 * canUseObjects,
+ 4);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"),
- (canUseFilledNewArrayOfStringObjects(parameters) ? 4 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 5 : 0),
+ 4 * canUseStrings + 5 * canUseObjects,
5);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m4"),
- 0,
- compilationMode.isDebug() ? 2 : 1);
+ 4 * canUseStrings + 5 * canUseObjects,
+ 5);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m5"),
- (canUseFilledNewArrayOfStringObjects(parameters) ? 4 : 0)
- + (canUseFilledNewArrayOfNonStringObjects(parameters) ? 1 : 0),
+ 4 * canUseStrings + 1 * canUseObjects,
compilationMode.isDebug() ? 6 : 4);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java
index 4b6e64c..c27a171 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayTest.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -46,9 +45,8 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : 5,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -78,8 +76,8 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), true);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"));
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
index 3a364dd..7d8bba7 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithNonUniqueValuesTest.java
@@ -46,10 +46,8 @@
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "104");
- private void inspect(
- MethodSubject method, int constClasses, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int constClasses, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -68,12 +66,11 @@
}
private void inspectD8(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
maxMaterializingConstants == 2 ? 98 : 26,
- 104,
- false);
+ 104);
}
@Test
@@ -89,12 +86,11 @@
}
private void inspectR8(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
maxMaterializingConstants == 2 ? 32 : 26,
- 104,
- false);
+ 104);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
index c7f847b..3c7976f 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ConstClassArrayWithUniqueValuesTest.java
@@ -52,9 +52,8 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -86,9 +85,9 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
deleted file mode 100644
index e28a4fc..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static com.android.tools.r8.cf.methodhandles.fields.ClassFieldMethodHandleTest.Main.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFillArrayData;
-import com.android.tools.r8.dex.code.DexNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class FilledArrayDataWithCatchHandlerTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public FilledArrayDataWithCatchHandlerTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkInstructions);
- }
-
- private void checkInstructions(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- List<InstructionSubject> newArrays =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexNewArray)
- .collect(Collectors.toList());
- assertEquals(1, newArrays.size());
-
- List<InstructionSubject> fillArrays =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFillArrayData)
- .collect(Collectors.toList());
- assertEquals(1, fillArrays.size());
-
- InstructionOffsetSubject offsetNew = newArrays.get(0).getOffset(foo);
- InstructionOffsetSubject offsetFill = newArrays.get(0).getOffset(foo);
- assertTrue(
- foo.streamTryCatches()
- .allMatch(r -> r.getRange().includes(offsetNew) && r.getRange().includes(offsetFill)));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- int[] array = null;
- try {
- array = new int[6];
- } catch (RuntimeException e) {
- return array[0];
- }
- array[0] = value;
- array[1] = value;
- array[2] = value;
- array[3] = value;
- array[4] = value;
- array[5] = value;
- return array[5];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
deleted file mode 100644
index f9e3ffe..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NewArrayInCatchRangeTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayInCatchRangeTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkInstructions);
- }
-
- private void checkInstructions(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- List<InstructionSubject> filledArrayInstructions =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
- .collect(Collectors.toList());
- assertEquals(1, filledArrayInstructions.size());
- InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
- assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- int[] array = null;
- try {
- array = new int[1];
- } catch (Exception e) {
- return array == null ? -1 : array.length;
- }
- array[0] = value;
- return array[0];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
deleted file mode 100644
index 3ecd9de..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NewArrayInTwoCatchRangesTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayInTwoCatchRangesTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkHasFilledNewArray);
- }
-
- private void checkHasFilledNewArray(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- assertTrue(
- foo.streamInstructions()
- .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- try {
- int[] array = new int[2];
- try {
- array[0] = value;
- try {
- array[1] = value;
- } catch (RuntimeException e) {
- return array[1];
- }
- } catch (RuntimeException e) {
- return array[0];
- }
- return array[0];
- } catch (RuntimeException e) {
- return 42;
- }
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
deleted file mode 100644
index c7e3b2a..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class NewArrayMonitorTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayMonitorTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayMonitorTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayMonitorTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkInstructions);
- }
-
- private void checkInstructions(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- List<InstructionSubject> filledArrayInstructions =
- foo.streamInstructions()
- .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
- .collect(Collectors.toList());
- assertEquals(1, filledArrayInstructions.size());
- InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
- assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
- }
-
- static class TestClass {
-
- public static synchronized int foo() {
- int value = 1;
- int[] array = new int[1];
- try {
- array[0] = value;
- } catch (RuntimeException e) {
- return array[0];
- }
- return array[0];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayOfInaccessibleTypeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayOfInaccessibleTypeTest.java
new file mode 100644
index 0000000..2282a4a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayOfInaccessibleTypeTest.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2024, 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.rewrite.arrays;
+
+import com.android.tools.r8.NoAccessModification;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+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;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class NewArrayOfInaccessibleTypeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeCfRuntime();
+ testForJvm(parameters)
+ .addProgramClassFileData(getProgramClassFileData())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addProgramClassFileData(getProgramClassFileData())
+ .release()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .enableNoAccessModificationAnnotationsForClasses()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ private static List<byte[]> getProgramClassFileData() throws Exception {
+ return ImmutableList.of(
+ transformer(Main.class)
+ .transformTypeInsnInMethod(
+ "main",
+ (int opcode, String type, MethodVisitor visitor) ->
+ visitor.visitTypeInsn(
+ opcode,
+ type.equals(binaryName(Inaccessible.class)) ? "pkg/Inaccessible" : type))
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(Inaccessible.class), "Lpkg/Inaccessible;")
+ .transform(),
+ transformer(Inaccessible.class).setClassDescriptor("Lpkg/Inaccessible;").transform());
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ // Moving the array creation into the else branch would change semantics as the code then no
+ // longer throws IllegalAccessError.
+ Inaccessible[] array = new Inaccessible[3];
+ array[0] = null;
+ array[1] = null;
+ if (System.currentTimeMillis() > 0) {
+ throw new RuntimeException("Unexpected");
+ } else {
+ array[2] = null;
+ System.out.println(Arrays.toString(array));
+ }
+ }
+ }
+
+ // TODO(b/320445632): Access modifier should not publicize items with illegal accesses.
+ @NoAccessModification
+ static class /*pkg.*/ Inaccessible {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
deleted file mode 100644
index a3fd2da..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2022, 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.rewrite.arrays;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.dex.code.DexFilledNewArray;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.Collections;
-import java.util.stream.Collectors;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-// Regression test for issue found in b/259986613
-@RunWith(Parameterized.class)
-public class NewArrayPutInCatchRangeTest extends TestBase {
-
- static final String EXPECTED = StringUtils.lines("1");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public NewArrayPutInCatchRangeTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- testForRuntime(parameters)
- .addInnerClasses(NewArrayPutInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @Test
- public void testReleaseD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8(parameters.getBackend())
- .release()
- .setMinApi(parameters)
- .addInnerClasses(NewArrayPutInCatchRangeTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED)
- .inspect(this::checkHasFilledNewArray);
- }
-
- private void checkHasFilledNewArray(CodeInspector inspector) {
- MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
- assertTrue(
- foo.streamInstructions()
- .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
- assertEquals(Collections.emptyList(), foo.streamTryCatches().collect(Collectors.toList()));
- }
-
- static class TestClass {
-
- public static int foo() {
- int value = 1;
- int[] array = new int[1];
- try {
- array[0] = value;
- } catch (RuntimeException e) {
- return array[0];
- }
- return array[0];
- }
-
- public static void main(String[] args) {
- System.out.println(foo());
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
index c552566..5ed624e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
@@ -72,7 +72,7 @@
int[] array;
synchronized (TestClass.class) {
array = new int[1];
- } // monitor exit here prohibits optimization as its failure could observe the lack of init.
+ }
array[0] = value;
return array[0];
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
index c80436c..7011411 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -63,7 +63,18 @@
"[1, null, 2]",
"[1, null, 2]",
"[1]",
- "[1, 2]",
+ "[1, 2, 3]",
+ "[2, 2, 2]",
+ "[6, 7]",
+ "[7]",
+ "[3, 4]",
+ "[99]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
+ "[0, 1]",
"[1, 2, 3, 4, 5]",
"[1]",
"[a, 1, null, d, e, f]",
@@ -91,12 +102,16 @@
"[0, 1, 2, 3, 4]",
"[4, 0, 0, 0, 0]",
"[4, 1, 2, 3, 4]",
+ "[9]",
+ "[*]",
+ "[*]",
+ "finally: [1, 2]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1, 2]",
"[0, 1, 2]",
"[0]",
- "[0, 1, 2]",
- "[1, 2, 3]",
- "[1, 2, 3, 4, 5, 6]",
- "[0]",
"[null, null]",
};
@@ -176,6 +191,20 @@
mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+ MethodSubject phiFilledNewArrayBlocks =
+ mainClass.uniqueMethodWithOriginalName("phiFilledNewArrayBlocks");
+ MethodSubject arrayWithDominatingPhiUsers =
+ mainClass.uniqueMethodWithOriginalName("arrayWithDominatingPhiUsers");
+ MethodSubject arrayWithNonDominatingPhiUsers =
+ mainClass.uniqueMethodWithOriginalName("arrayWithNonDominatingPhiUsers");
+ MethodSubject phiWithExceptionalPhiUser =
+ mainClass.uniqueMethodWithOriginalName("phiWithExceptionalPhiUser");
+ MethodSubject phiWithNestedCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("phiWithNestedCatchHandler");
+ MethodSubject multiUseArray = mainClass.uniqueMethodWithOriginalName("multiUseArray");
+ MethodSubject arrayWithHole = mainClass.uniqueMethodWithOriginalName("arrayWithHole");
+ MethodSubject reassignmentDoesNotOptimize =
+ mainClass.uniqueMethodWithOriginalName("reassignmentDoesNotOptimize");
MethodSubject intsThatUseFilledNewArray =
mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
MethodSubject twoDimensionalArrays =
@@ -191,26 +220,60 @@
mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
MethodSubject arrayWithExtraInitialPuts =
mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
- MethodSubject catchHandlerThrowing =
- mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
- MethodSubject catchHandlerNonThrowingFilledNewArray =
- mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
- MethodSubject catchHandlerNonThrowingFillArrayData =
- mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+ MethodSubject catchHandlerWithoutSideeffects =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerWithoutSideeffects");
+ MethodSubject allocationWithCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("allocationWithCatchHandler");
+ MethodSubject allocationWithoutCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("allocationWithoutCatchHandler");
+ MethodSubject catchHandlerWithFinally =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerWithFinally");
+ MethodSubject simpleSynchronized1 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized1");
+ MethodSubject simpleSynchronized2 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized2");
+ MethodSubject simpleSynchronized3 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized3");
+ MethodSubject simpleSynchronized4 =
+ mainClass.uniqueMethodWithOriginalName("simpleSynchronized4");
+ MethodSubject arrayInsideCatchHandler =
+ mainClass.uniqueMethodWithOriginalName("arrayInsideCatchHandler");
MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+ // The explicit assignments can't be collapsed without breaking the debugger's ability to
+ // visit each line.
+ Class<?> filledNewArrayInRelease =
+ compilationMode == CompilationMode.DEBUG ? DexNewArray.class : DexFilledNewArray.class;
+
assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
assertFilledArrayData(arraysThatUseFilledData);
- assertFilledArrayData(catchHandlerNonThrowingFillArrayData);
- if (compilationMode == CompilationMode.DEBUG) {
- // The explicit assignments can't be collapsed without breaking the debugger's ability to
- // visit each line.
- assertArrayTypes(reversedArray, DexNewArray.class);
- } else {
- assertArrayTypes(reversedArray, DexFilledNewArray.class);
- }
+ // Algorithm does not support out-of-order assignment.
+ assertArrayTypes(reversedArray, DexNewArray.class);
+ // Algorithm does not support assigning to array elements multiple times.
+ assertArrayTypes(reassignmentDoesNotOptimize, DexNewArray.class);
+ // Algorithm does not support default-initialized array elements.
+ assertArrayTypes(arrayWithHole, DexNewArray.class);
+ // ArrayPuts not dominated by return statement.
+ assertArrayTypes(phiFilledNewArrayBlocks, DexNewArray.class);
+ assertArrayTypes(arrayWithDominatingPhiUsers, filledNewArrayInRelease);
+ assertArrayTypes(arrayWithNonDominatingPhiUsers, DexNewArray.class);
+ assertArrayTypes(phiWithNestedCatchHandler, DexNewArray.class);
+ assertArrayTypes(phiWithExceptionalPhiUser, DexFilledNewArray.class, filledNewArrayInRelease);
+ // Not safe to change catch handlers.
+ assertArrayTypes(allocationWithoutCatchHandler, DexNewArray.class);
+ // Not safe to change catch handlers.
+ assertArrayTypes(allocationWithCatchHandler, DexNewArray.class);
+ assertArrayTypes(catchHandlerWithFinally, DexNewArray.class);
+ assertArrayTypes(simpleSynchronized1, DexFilledNewArray.class);
+ assertArrayTypes(simpleSynchronized2, DexFilledNewArray.class);
+ assertArrayTypes(simpleSynchronized3, DexNewArray.class);
+ assertArrayTypes(simpleSynchronized4, DexNewArray.class);
+ // Could be optimized if we had side-effect analysis of exceptional blocks.
+ assertArrayTypes(catchHandlerWithoutSideeffects, DexNewArray.class);
+ assertArrayTypes(arrayInsideCatchHandler, filledNewArrayInRelease);
+ assertArrayTypes(multiUseArray, filledNewArrayInRelease);
if (!canUseFilledNewArrayOfStringObjects(parameters)) {
assertArrayTypes(stringArrays, DexNewArray.class);
@@ -239,9 +302,7 @@
assertArrayTypes(referenceArraysWithInterfaceImplementations, DexNewArray.class);
}
- // TODO(b/246971330): Add support for arrays whose values have conditionals.
- // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
-
+ assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
assertArrayTypes(
objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class, DexNewArray.class);
@@ -261,9 +322,6 @@
// haven't bothered.
assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
-
- assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
- assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
}
private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
@@ -310,6 +368,14 @@
referenceArraysWithInterfaceImplementations();
interfaceArrayWithRawObject();
phiFilledNewArray();
+ phiFilledNewArrayBlocks();
+ arrayWithDominatingPhiUsers();
+ arrayWithNonDominatingPhiUsers();
+ phiWithNestedCatchHandler();
+ phiWithExceptionalPhiUser();
+ multiUseArray();
+ arrayWithHole();
+ reassignmentDoesNotOptimize();
intsThatUseFilledNewArray();
twoDimensionalArrays();
objectArraysFilledNewArrayRange();
@@ -318,9 +384,15 @@
reversedArray();
arrayWithCorrectCountButIncompleteCoverage();
arrayWithExtraInitialPuts();
- catchHandlerThrowing();
- catchHandlerNonThrowingFilledNewArray();
- catchHandlerNonThrowingFillArrayData();
+ arrayInsideCatchHandler();
+ allocationWithCatchHandler();
+ allocationWithoutCatchHandler();
+ catchHandlerWithFinally();
+ simpleSynchronized1();
+ simpleSynchronized2();
+ simpleSynchronized3();
+ simpleSynchronized4();
+ catchHandlerWithoutSideeffects();
arrayIntoAnotherArray();
assumedValues();
}
@@ -411,21 +483,12 @@
}
@NeverInline
- private static void catchHandlerNonThrowingFilledNewArray() {
+ private static void arrayInsideCatchHandler() {
try {
- int[] arr1 = {1, 2, 3};
+ // Test filled-new-array with a throwing instruction before the last array-put.
+ int[] arr = new int[1];
System.currentTimeMillis();
- System.out.println(Arrays.toString(arr1));
- } catch (Throwable t) {
- throw new RuntimeException(t);
- }
- }
-
- @NeverInline
- private static void catchHandlerNonThrowingFillArrayData() {
- try {
- int[] arr = {1, 2, 3, 4, 5, 6};
- System.currentTimeMillis();
+ arr[0] = 9;
System.out.println(Arrays.toString(arr));
} catch (Throwable t) {
throw new RuntimeException(t);
@@ -433,37 +496,115 @@
}
@NeverInline
- private static void catchHandlerThrowing() {
- int[] arr1 = new int[3];
- arr1[0] = 0;
- arr1[1] = 1;
- // Since the array is used in only one spot, and that spot is not within the try/catch, it
- // should be safe to use filled-new-array, but we don't.
+ private static void allocationWithCatchHandler() {
+ Object[] arr;
try {
- System.currentTimeMillis();
+ arr = new Object[1];
+ } catch (NoClassDefFoundError | OutOfMemoryError t) {
+ throw new RuntimeException(t);
+ }
+
+ // new-array-empty dominates this, but catch handlers are relevant
+ arr[0] = "*";
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void allocationWithoutCatchHandler() {
+ Object[] arr = new Object[1];
+ try {
+ // new-array-empty dominates this, but catch handlers are relevant.
+ arr[0] = "*";
+ } catch (NoClassDefFoundError | OutOfMemoryError t) {
+ throw new RuntimeException(t);
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void catchHandlerWithFinally() {
+ Object[] arr = new Object[2];
+ try {
+ System.out.print("finally: ");
+ } finally {
+ // This will be duplicated into the throwing and non-throwing blocks.
+ arr[0] = "1";
+ }
+ arr[1] = "2";
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void simpleSynchronized1() {
+ // Should optimize since array is contained within a try block.
+ synchronized (Main.class) {
+ int[] arr = new int[] {1, 2};
+ System.out.println(Arrays.toString(arr));
+ }
+ }
+
+ @NeverInline
+ private static synchronized void simpleSynchronized2() {
+ // Should optimize since array is contained within a try block.
+ try {
+ try {
+ int[] arr = new int[] {1, 2};
+ System.out.println(Arrays.toString(arr));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ } finally {
+ System.currentTimeMillis();
+ }
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+
+ @NeverInline
+ private static void simpleSynchronized3() {
+ // Does not optimize because allocation has different catch handlers.
+ int[] arr = new int[2];
+ synchronized (Main.class) {
+ arr[0] = 1;
+ arr[1] = 2;
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void simpleSynchronized4() {
+ // Does not optimize because allocation has different catch handlers.
+ int[] arr;
+ synchronized (Main.class) {
+ arr = new int[2];
+ }
+ arr[0] = 1;
+ arr[1] = 2;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void catchHandlerWithoutSideeffects() {
+ // If we added logic to show that catch handlers exit without side-effects, we could optimize
+ // this case.z
+ int[] arr1;
+ try {
+ arr1 = new int[3];
+ } catch (Throwable t) {
+ throw new RuntimeException("1");
+ }
+ try {
+ arr1[0] = 0;
+ } catch (Throwable t) {
+ throw new RuntimeException("2");
+ }
+ arr1[1] = 1;
+ try {
arr1[2] = 2;
} catch (Throwable t) {
- throw new RuntimeException(t);
+ throw new RuntimeException("3");
}
System.out.println(Arrays.toString(arr1));
-
- try {
- // Test filled-new-array with a throwing instruction before the last array-put.
- int[] arr2 = new int[1];
- System.currentTimeMillis();
- arr2[0] = 0;
- System.out.println(Arrays.toString(arr2));
-
- // Test filled-array-data with a throwing instruction before the last array-put.
- short[] arr3 = new short[3];
- arr3[0] = 0;
- arr3[1] = 1;
- System.currentTimeMillis();
- arr3[2] = 2;
- System.out.println(Arrays.toString(arr3));
- } catch (Throwable t) {
- throw new RuntimeException(t);
- }
}
@NeverInline
@@ -485,11 +626,123 @@
@NeverInline
private static void phiFilledNewArray() {
// The presence of ? should not affect use of filled-new-array.
- Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+ Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3, 3};
System.out.println(Arrays.toString(phiArray));
}
@NeverInline
+ private static void phiFilledNewArrayBlocks() {
+ int[] phiArray = new int[3];
+ if (System.currentTimeMillis() > 0) {
+ phiArray[0] = 2;
+ phiArray[1] = 2;
+ phiArray[2] = 2;
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void arrayWithDominatingPhiUsers() {
+ int[] phiArray = null;
+ try {
+ phiArray = new int[2];
+ phiArray[0] = 6;
+ phiArray[1] = 7;
+ } catch (Throwable t) {
+ System.out.println("Not reached");
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void arrayWithNonDominatingPhiUsers() {
+ int[] phiArray = null;
+ try {
+ phiArray = new int[1];
+ // If currentTimeMillis() throws, phiArray will have value of [0].
+ phiArray[0] = System.currentTimeMillis() > 0 ? 7 : 0;
+ } catch (Throwable t) {
+ System.out.println("Not reached");
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void phiWithNestedCatchHandler() {
+ int[] phiArray = null;
+ try {
+ phiArray = new int[2];
+ // If currentTimeMillis() throws, phiArray will have value of [0, 0].
+ try {
+ System.currentTimeMillis();
+ } catch (RuntimeException r) {
+ throw new RuntimeException(r);
+ }
+ phiArray[0] = 3;
+ phiArray[1] = 4;
+ } catch (Throwable t) {
+ System.out.println("Not reached");
+ }
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void phiWithExceptionalPhiUser() {
+ int[] arr = null;
+ try {
+ // Both of these should optimize, but care must be taken to ensure the phiUsers are properly
+ // dominated post-optimization.
+ if (System.currentTimeMillis() > 0) {
+ arr = new int[1];
+ arr[0] = 99;
+ } else {
+ arr = new int[] {1, 2};
+ }
+ } catch (RuntimeException e) {
+ // fall through
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void multiUseArray() {
+ int[] arr = new int[2];
+ arr[0] = 0;
+ arr[1] = System.nanoTime() > 0 ? 1 : 2;
+ System.out.println(Arrays.toString(arr));
+ System.out.println(Arrays.toString(arr));
+ // Usage in a different basic block.
+ if (System.nanoTime() > 0) {
+ System.nanoTime();
+ }
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void arrayWithHole() {
+ int[] arr = new int[2];
+ arr[1] = 1;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void reassignmentDoesNotOptimize() {
+ // Reassignment in same block, and of last index.
+ Integer[] arr = new Integer[2];
+ arr[0] = 0;
+ arr[1] = 2;
+ arr[1] = 1;
+ System.out.println(Arrays.toString(arr));
+
+ // Reassignment across blocks, of non-last index.
+ arr = new Integer[2];
+ arr[0] = 3;
+ arr[1] = System.nanoTime() > 0 ? 1 : 2;
+ arr[0] = 0;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
private static void intsThatUseFilledNewArray() {
// Up to 5 ints uses filled-new-array rather than filled-array-data.
int[] intArr = {1, 2, 3, 4, 5};
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
index be54d34..68b80fc 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithNonUniqueValuesTest.java
@@ -46,9 +46,9 @@
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "50");
- private void inspect(MethodSubject method, int staticGets, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int staticGets, int puts, boolean isD8) {
+ // D8 cannot optimize due to risk of NoClassDefFoundError.
+ boolean expectingFilledNewArray = !isD8 && canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -66,18 +66,9 @@
}
private void inspectD8(CodeInspector inspector) {
- inspect(
- inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"),
- canUseFilledNewArrayOfNonStringObjects(parameters) ? 100 : 1,
- 100,
- false);
- inspect(
- inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
- canUseFilledNewArrayOfNonStringObjects(parameters)
- ? 50
- : (maxMaterializingConstants == 2 ? 42 : 10),
- 50,
- false);
+ // D8 cannot optimize due to risk of NoClassDefFoundError.
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 100, 100, true);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 50, 50, true);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
index fce1e79..f64d0e8 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StaticGetArrayWithUniqueValuesTest.java
@@ -52,9 +52,9 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfNonStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(boolean isR8, MethodSubject method, int puts) {
+ // D8 cannot optimize due to risk of NoClassDefFoundError.
+ boolean expectingFilledNewArray = isR8 && canUseFilledNewArrayOfNonStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -84,10 +84,10 @@
}
}
- private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ private void inspect(CodeInspector inspector, boolean isR8) {
+ inspect(isR8, inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5);
+ inspect(isR8, inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5);
+ inspect(isR8, inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100);
}
@Test
@@ -97,7 +97,7 @@
.addInnerClasses(getClass())
.setMinApi(parameters)
.run(parameters.getRuntime(), TestClass.class)
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, false))
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
@@ -110,7 +110,7 @@
.enableInliningAnnotations()
.addDontObfuscate()
.run(parameters.getRuntime(), TestClass.class)
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, true))
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
index c433e6d..cf12961 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithNonUniqueValuesTest.java
@@ -46,10 +46,8 @@
private static final String EXPECTED_OUTPUT = StringUtils.lines("100", "104");
- private void inspect(
- MethodSubject method, int constStrings, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int constStrings, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -68,12 +66,11 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 1, 100);
inspect(
inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"),
maxMaterializingConstants == 2 ? 32 : 26,
- 104,
- false);
+ 104);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
index 837bea2..53f7f1f 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/StringArrayWithUniqueValuesTest.java
@@ -52,9 +52,8 @@
EXPECTING_APUTOBJECT
}
- private void inspect(MethodSubject method, int puts, boolean insideCatchHandler) {
- boolean expectingFilledNewArray =
- canUseFilledNewArrayOfStringObjects(parameters) && !insideCatchHandler;
+ private void inspect(MethodSubject method, int puts) {
+ boolean expectingFilledNewArray = canUseFilledNewArrayOfStringObjects(parameters);
assertEquals(
expectingFilledNewArray ? 0 : puts,
method.streamInstructions().filter(InstructionSubject::isArrayPut).count());
@@ -86,9 +85,9 @@
}
private void inspect(CodeInspector inspector) {
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5, false);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5, true);
- inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100, false);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m1"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m2"), 5);
+ inspect(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m3"), 100);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfApiDependentLibraryTypeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfApiDependentLibraryTypeTest.java
new file mode 100644
index 0000000..1f49507
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfApiDependentLibraryTypeTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2024, 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.rewrite.arrays;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.function.Function;
+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 UnusedNewArrayOfApiDependentLibraryTypeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeCfRuntime();
+ testForJvm(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addInnerClasses(getClass())
+ .release()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ hasFunctionAsRunTime(),
+ TestRunResult::assertSuccessWithEmptyOutput,
+ runResult -> runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .applyIf(
+ !hasFunctionAtCompileTime(), testBuilder -> testBuilder.addDontWarn(Function.class))
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ hasFunctionAsRunTime(),
+ TestRunResult::assertSuccessWithEmptyOutput,
+ runResult -> runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(canOptimize(), mainMethodSubject.getMethod().getCode().isEmptyVoidMethod());
+ }
+
+ private boolean canOptimize() {
+ return hasFunctionAtCompileTime() && parameters.isDexRuntime();
+ }
+
+ private boolean hasFunctionAtCompileTime() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ private boolean hasFunctionAsRunTime() {
+ return parameters.isCfRuntime()
+ || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0);
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Function[] functions = new Function[0];
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfInaccessibleTypeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfInaccessibleTypeTest.java
new file mode 100644
index 0000000..aaf7c49
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfInaccessibleTypeTest.java
@@ -0,0 +1,88 @@
+// Copyright (c) 2024, 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.rewrite.arrays;
+
+import com.android.tools.r8.NoAccessModification;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+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;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class UnusedNewArrayOfInaccessibleTypeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeCfRuntime();
+ testForJvm(parameters)
+ .addProgramClassFileData(getProgramClassFileData())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addProgramClassFileData(getProgramClassFileData())
+ .release()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .enableNoAccessModificationAnnotationsForClasses()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ private static List<byte[]> getProgramClassFileData() throws Exception {
+ return ImmutableList.of(
+ transformer(Main.class)
+ .transformTypeInsnInMethod(
+ "main",
+ (int opcode, String type, MethodVisitor visitor) ->
+ visitor.visitTypeInsn(
+ opcode,
+ type.equals(binaryName(Inaccessible.class)) ? "pkg/Inaccessible" : type))
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(Inaccessible.class), "Lpkg/Inaccessible;")
+ .transform(),
+ transformer(Inaccessible.class).setClassDescriptor("Lpkg/Inaccessible;").transform());
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Inaccessible[] array = new Inaccessible[0];
+ }
+ }
+
+ // TODO(b/320445632): Access modifier should not publicize items with illegal accesses.
+ @NoAccessModification
+ static class /*pkg.*/ Inaccessible {}
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfKnownLibraryTypeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfKnownLibraryTypeTest.java
new file mode 100644
index 0000000..a013ceb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/UnusedNewArrayOfKnownLibraryTypeTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2024, 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.rewrite.arrays;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 UnusedNewArrayOfKnownLibraryTypeTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeCfRuntime();
+ testForJvm(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8()
+ .addInnerClasses(getClass())
+ .release()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(mainMethodSubject.getMethod().getCode().isEmptyVoidMethod());
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ // Primitives.
+ boolean[] booleans = new boolean[0];
+ byte[] bytes = new byte[0];
+ char[] chars = new char[0];
+ double[] doubles = new double[0];
+ float[] floats = new float[0];
+ int[] ints = new int[0];
+ long[] longs = new long[0];
+ short[] shorts = new short[0];
+
+ // Boxed primitives.
+ Boolean[] boxedBooleans = new Boolean[0];
+ Byte[] boxedBytes = new Byte[0];
+ Character[] boxedChars = new Character[0];
+ Double[] boxedDoubles = new Double[0];
+ Float[] boxedFloats = new Float[0];
+ Integer[] boxedInts = new Integer[0];
+ Long[] boxedLongs = new Long[0];
+ Short[] boxedShorts = new Short[0];
+
+ // Common classes.
+ Enum<?>[] enums = new Enum<?>[0];
+ Object[] objects = new Object[0];
+ String[] strings = new String[0];
+ StringBuilder[] stringBuilders = new StringBuilder[0];
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 2e03043..a2a1119 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -95,7 +95,7 @@
"shaking1.Used -> a.a:",
"# {'id':'sourceFile','fileName':'Used.java'}",
" java.lang.String name -> a",
- " 1:14:void <init>(java.lang.String):0:13 -> <init>",
+ " 1:2:void <init>(java.lang.String):12:13 -> <init>",
" 1:1:java.lang.String method():17:17 -> a",
" 1:1:java.lang.String aMethodThatIsNotUsedButKept():21:21 "
+ "-> aMethodThatIsNotUsedButKept");
diff --git a/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
index 5d90382..c46d024 100644
--- a/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingTest.java
@@ -10,15 +10,15 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.R8FullTestBuilder;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.FileNotFoundException;
import java.nio.file.Path;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -115,35 +115,32 @@
.inspect(this::inspect)
.writeToZip();
- R8FullTestBuilder builder =
- testForR8(parameters.getBackend())
- .addLibraryClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class)
- .addDefaultRuntimeLibrary(parameters)
- .addProgramClasses(Caller.class)
- .addKeepMainRule(Caller.class)
- .setMinApi(parameters);
- R8TestRunResult result =
- builder
- .compile()
- .addRunClasspathFiles(firstRunArchive)
- .run(parameters.getRuntime(), Caller.class);
- // TODO(b/117302947): Dex runtime should be able to find that framework class.
- if (parameters.isDexRuntime()) {
- result.assertFailureWithErrorThatMatches(containsString("NoClassDefFoundError"));
- result.assertFailureWithErrorThatMatches(containsString("android.util.Log"));
- return;
- }
- result
- .assertFailureWithErrorThatMatches(
- containsString(createExpectedMessage(UninstantiatedClass.class)))
- .assertFailureWithErrorThatMatches(containsString("void <init>()"))
- .assertFailureWithErrorThatMatches(containsString("void <init>(java.lang.String)"))
- .assertFailureWithErrorThatMatches(
- containsString(createExpectedMessage(TestClass.class)))
- .assertFailureWithErrorThatMatches(containsString("void foo(int,long)"))
- .assertFailureWithErrorThatMatches(
- containsString("void bar(" + PACKAGE_NAME + ".TestClass" +")"))
- .assertFailureWithErrorThatMatches(containsString("Reaching the end"));
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(BaseClass.class, UninstantiatedClass.class, TestClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addProgramClasses(Caller.class)
+ .addKeepMainRule(Caller.class)
+ .setMinApi(parameters)
+ .compile()
+ .addRunClasspathFiles(firstRunArchive)
+ .run(parameters.getRuntime(), Caller.class)
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V6_0_1),
+ runResult -> runResult.assertFailureWithErrorThatThrows(FileNotFoundException.class),
+ runResult ->
+ runResult
+ .assertFailureWithErrorThatMatches(
+ containsString(createExpectedMessage(UninstantiatedClass.class)))
+ .assertFailureWithErrorThatMatches(containsString("void <init>()"))
+ .assertFailureWithErrorThatMatches(
+ containsString("void <init>(java.lang.String)"))
+ .assertFailureWithErrorThatMatches(
+ containsString(createExpectedMessage(TestClass.class)))
+ .assertFailureWithErrorThatMatches(containsString("void foo(int,long)"))
+ .assertFailureWithErrorThatMatches(
+ containsString("void bar(" + PACKAGE_NAME + ".TestClass" + ")"))
+ .assertFailureWithErrorThatMatches(containsString("Reaching the end")));
}
private String createExpectedMessage(Class<?> clazz) {
diff --git a/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingWithInliningTest.java b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingWithInliningTest.java
index edd31ba..c916ae9 100644
--- a/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingWithInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/addconfigurationdebugging/ConfigurationDebuggingWithInliningTest.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.io.FileNotFoundException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -39,13 +41,9 @@
.run(parameters.getRuntime(), Main.class)
// AddConfigurationDebugging will insert a call to android.util.log.
.applyIf(
- parameters.isDexRuntime(),
- result ->
- result
- .assertFailureWithErrorThatThrows(NoClassDefFoundError.class)
- .assertFailureWithErrorThatMatches(containsString("Landroid/util/Log;")))
- .applyIf(
- parameters.isCfRuntime(),
+ parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V5_1_1, Version.V6_0_1),
+ result -> result.assertFailureWithErrorThatThrows(FileNotFoundException.class),
result ->
result.assertFailureWithErrorThatMatches(
containsString("Missing method in " + typeName(Bar.class))));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
index 5b8f002..bf038dd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
@@ -8,15 +8,20 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
+import java.util.HashSet;
+import java.util.Set;
public class VerticallyMergedClassesInspector {
private final DexItemFactory dexItemFactory;
private final VerticallyMergedClasses verticallyMergedClasses;
+ private final Set<ClassReference> seen = new HashSet<>();
+
public VerticallyMergedClassesInspector(
DexItemFactory dexItemFactory, VerticallyMergedClasses verticallyMergedClasses) {
this.dexItemFactory = dexItemFactory;
@@ -24,8 +29,7 @@
}
public VerticallyMergedClassesInspector assertMergedIntoSubtype(Class<?> clazz) {
- assertTrue(verticallyMergedClasses.hasBeenMergedIntoSubtype(toDexType(clazz, dexItemFactory)));
- return this;
+ return assertMergedIntoSubtype(Reference.classFromClass(clazz));
}
public VerticallyMergedClassesInspector assertMergedIntoSubtype(Class<?>... classes) {
@@ -39,6 +43,7 @@
assertTrue(
verticallyMergedClasses.hasBeenMergedIntoSubtype(
toDexType(classReference, dexItemFactory)));
+ seen.add(classReference);
return this;
}
@@ -53,4 +58,11 @@
assertTrue(verticallyMergedClasses.isEmpty());
return this;
}
+
+ public VerticallyMergedClassesInspector assertNoOtherClassesMerged() {
+ for (DexType source : verticallyMergedClasses.getSources()) {
+ assertTrue(source.getTypeName(), seen.contains(source.asClassReference()));
+ }
+ return this;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java b/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
index 3f7aba6..53794e3 100644
--- a/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
+++ b/src/test/java/com/android/tools/r8/workaround/FilledNewArrayFromSubtypeWithMissingInterfaceWorkaroundTest.java
@@ -6,14 +6,13 @@
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.assertFalse;
import com.android.tools.r8.NeverInline;
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.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -44,7 +43,7 @@
.release()
.setMinApi(parameters)
.compile()
- .inspect(inspector -> inspect(inspector, true))
+ .inspect(this::inspect)
.apply(
compileResult ->
compileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors())
@@ -66,18 +65,17 @@
parameters.isDexRuntime(),
compileResult ->
compileResult
- .inspect(inspector -> inspect(inspector, false))
+ .inspect(this::inspect)
.runDex2Oat(parameters.getRuntime())
.assertNoVerificationErrors())
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
}
- private void inspect(CodeInspector inspector, boolean isD8) {
+ private void inspect(CodeInspector inspector) {
MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
assertThat(mainMethodSubject, isPresent());
- assertEquals(
- isD8 && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N),
+ assertFalse(
mainMethodSubject.streamInstructions().anyMatch(InstructionSubject::isFilledNewArray));
}
diff --git a/tools/run_on_app_dump.py b/tools/run_on_app_dump.py
index 964d1dc..9ca17ac 100755
--- a/tools/run_on_app_dump.py
+++ b/tools/run_on_app_dump.py
@@ -299,6 +299,7 @@
'url': 'https://github.com/signalapp/Signal-Android',
'revision': '91ca19f294362ccee2c2b43c247eba228e2b30a1',
'folder': 'signal-android',
+ 'golem_duration': 300
}),
# TODO(b/172815827): Monkey runner does not work
App({