Merge commit 'd293c56c5ce265bb4e423e3beef2418c6074519f' into dev-release
diff --git a/build.gradle b/build.gradle
index 6429c57..f016148 100644
--- a/build.gradle
+++ b/build.gradle
@@ -44,8 +44,8 @@
mockitoVersion = '2.10.0'
// The kotlin version is only here to specify the kotlin language level,
// all kotlin compilations are done in tests.
- kotlinVersion = '1.6.0'
- kotlinExtMetadataJVMVersion = '0.5.0'
+ kotlinVersion = '1.8.0'
+ kotlinExtMetadataJVMVersion = '0.6.0'
smaliVersion = '2.2b4'
errorproneVersion = '2.3.2'
testngVersion = '6.10'
@@ -285,7 +285,7 @@
main17Implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
main17Implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
main17Implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
-
+
examplesTestNGRunnerCompile group: 'org.testng', name: 'testng', version: testngVersion
testCompile sourceSets.examples.output
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index b17a3e1..8872494 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -17,6 +17,8 @@
/** Name with which other bindings, conditions or targets can reference the bound item pattern. */
String bindingName();
+ KeepItemKind kind() default KeepItemKind.DEFAULT;
+
String classFromBinding() default "";
String className() default "";
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index 1c7d3a5..c88085a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -97,10 +97,21 @@
public static final Class<KeepTarget> CLASS = KeepTarget.class;
public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String kind = "kind";
public static final String allow = "allow";
public static final String disallow = "disallow";
}
+ public static final class Kind {
+ public static final Class<KeepItemKind> CLASS = KeepItemKind.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+
+ public static final String DEFAULT = "DEFAULT";
+ public static final String ONLY_CLASS = "ONLY_CLASS";
+ public static final String ONLY_MEMBERS = "ONLY_MEMBERS";
+ public static final String CLASS_AND_MEMBERS = "CLASS_AND_MEMBERS";
+ }
+
public static final class Option {
public static final Class<KeepOption> CLASS = KeepOption.class;
public static final String DESCRIPTOR = getDescriptor(CLASS);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepItemKind.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepItemKind.java
new file mode 100644
index 0000000..7530bf1
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepItemKind.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2023, 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.annotations;
+
+public enum KeepItemKind {
+ ONLY_CLASS,
+ ONLY_MEMBERS,
+ CLASS_AND_MEMBERS,
+ DEFAULT
+}
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 be61b4a..a2e9c4f 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
@@ -26,6 +26,8 @@
@Retention(RetentionPolicy.CLASS)
public @interface KeepTarget {
+ KeepItemKind kind() default KeepItemKind.DEFAULT;
+
/**
* Define the options that do not need to be preserved for the target.
*
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 9fdf1f4..7a6de42 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
@@ -8,6 +8,7 @@
import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Kind;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Option;
import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
import com.android.tools.r8.keepanno.ast.KeepBindings;
@@ -21,6 +22,7 @@
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepItemKind;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
@@ -697,6 +699,7 @@
private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
private Parent<KeepItemReference> parent;
private String memberBindingReference = null;
+ private KeepItemKind kind = null;
private final ClassDeclaration classDeclaration = new ClassDeclaration();
private final ExtendsDeclaration extendsDeclaration = new ExtendsDeclaration();
private final MemberDeclaration memberDeclaration = new MemberDeclaration();
@@ -714,6 +717,29 @@
}
@Override
+ public void visitEnum(String name, String descriptor, String value) {
+ if (!descriptor.equals(KeepConstants.Kind.DESCRIPTOR)) {
+ super.visitEnum(name, descriptor, value);
+ }
+ switch (value) {
+ case Kind.DEFAULT:
+ // The default value is obtained by not assigning a kind (e.g., null in the builder).
+ break;
+ case Kind.ONLY_CLASS:
+ kind = KeepItemKind.ONLY_CLASS;
+ break;
+ case Kind.ONLY_MEMBERS:
+ kind = KeepItemKind.ONLY_MEMBERS;
+ break;
+ case Kind.CLASS_AND_MEMBERS:
+ kind = KeepItemKind.CLASS_AND_MEMBERS;
+ break;
+ default:
+ super.visitEnum(name, descriptor, value);
+ }
+ }
+
+ @Override
public void visit(String name, Object value) {
if (name.equals(Item.memberFromBinding) && value instanceof String) {
memberBindingReference = (String) value;
@@ -741,18 +767,29 @@
if (memberBindingReference != null) {
if (!classDeclaration.getValue().equals(classDeclaration.getDefaultValue())
|| !memberDeclaration.getValue().isNone()
- || !extendsDeclaration.getValue().isAny()) {
+ || !extendsDeclaration.getValue().isAny()
+ || kind != null) {
throw new KeepEdgeException(
"Cannot define an item explicitly and via a member-binding reference");
}
parent.accept(KeepItemReference.fromBindingReference(memberBindingReference));
} else {
+ KeepMemberPattern memberPattern = memberDeclaration.getValue();
+ // If the kind is not set (default) then the content of the members determines the kind.
+ if (kind == null) {
+ kind = memberPattern.isNone() ? KeepItemKind.ONLY_CLASS : KeepItemKind.ONLY_MEMBERS;
+ }
+ // If the kind is a member kind and no member pattern is set then set members to all.
+ if (!kind.equals(KeepItemKind.ONLY_CLASS) && memberPattern.isNone()) {
+ memberPattern = KeepMemberPattern.allMembers();
+ }
parent.accept(
KeepItemReference.fromItemPattern(
KeepItemPattern.builder()
+ .setKind(kind)
.setClassReference(classDeclaration.getValue())
.setExtendsPattern(extendsDeclaration.getValue())
- .setMemberPattern(memberDeclaration.getValue())
+ .setMemberPattern(memberPattern)
.build()));
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 774ea58..f3e97fa 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -112,7 +112,7 @@
// Default is "no methods".
return;
}
- if (memberPattern.isAll()) {
+ if (memberPattern.isAllMembers()) {
throw new Unimplemented();
}
if (memberPattern.isMethod()) {
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 8c890df..f1ed988 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
@@ -41,7 +41,9 @@
*
* ITEM_PATTERN
* ::= any
- * | class CLASS_REFERENCE extends EXTENDS_PATTERN { MEMBER_PATTERN }
+ * | ITEM_KIND class CLASS_REFERENCE extends EXTENDS_PATTERN { MEMBER_PATTERN }
+ *
+ * ITEM_KIND ::= ONLY_CLASS | ONLY_MEMBERS | CLASS_AND_MEMBERS
*
* TYPE_PATTERN ::= any | exact type-descriptor
* PACKAGE_PATTERN ::= any | exact package-name
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
index 1466fc1..dd104eb 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -11,6 +11,10 @@
return new Builder();
}
+ public static KeepFieldPattern allFields() {
+ return builder().build();
+ }
+
public static class Builder {
private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.any();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemKind.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemKind.java
new file mode 100644
index 0000000..0d59769
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemKind.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, 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;
+
+public enum KeepItemKind {
+ ONLY_CLASS,
+ ONLY_MEMBERS,
+ CLASS_AND_MEMBERS
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
index 6dfe7e4..6477a4c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -27,16 +27,21 @@
return new Builder();
}
+ public boolean isClassAndMemberPattern() {
+ return kind == KeepItemKind.CLASS_AND_MEMBERS;
+ }
+
public boolean isClassItemPattern() {
- return memberPattern.isNone();
+ return kind == KeepItemKind.ONLY_CLASS;
}
public boolean isMemberItemPattern() {
- return !memberPattern.isNone();
+ return kind == KeepItemKind.ONLY_MEMBERS;
}
public static class Builder {
+ private KeepItemKind kind = null;
private KeepClassReference classReference =
KeepClassReference.fromClassNamePattern(KeepQualifiedClassNamePattern.any());
private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
@@ -45,15 +50,22 @@
private Builder() {}
public Builder copyFrom(KeepItemPattern pattern) {
- return setClassReference(pattern.getClassReference())
+ return setKind(pattern.getKind())
+ .setClassReference(pattern.getClassReference())
.setExtendsPattern(pattern.getExtendsPattern())
.setMemberPattern(pattern.getMemberPattern());
}
public Builder any() {
+ kind = KeepItemKind.CLASS_AND_MEMBERS;
classReference = KeepClassReference.fromClassNamePattern(KeepQualifiedClassNamePattern.any());
extendsPattern = KeepExtendsPattern.any();
- memberPattern = KeepMemberPattern.all();
+ memberPattern = KeepMemberPattern.allMembers();
+ return this;
+ }
+
+ public Builder setKind(KeepItemKind kind) {
+ this.kind = kind;
return this;
}
@@ -77,29 +89,54 @@
}
public KeepItemPattern build() {
- return new KeepItemPattern(classReference, extendsPattern, memberPattern);
+ if (kind == null) {
+ kind = memberPattern.isNone() ? KeepItemKind.ONLY_CLASS : KeepItemKind.ONLY_MEMBERS;
+ }
+ if (kind == KeepItemKind.ONLY_CLASS && !memberPattern.isNone()) {
+ throw new KeepEdgeException(
+ "Invalid kind ONLY_CLASS for item with member pattern: " + memberPattern);
+ }
+ if (kind == KeepItemKind.ONLY_MEMBERS && memberPattern.isNone()) {
+ throw new KeepEdgeException("Invalid kind ONLY_MEMBERS for item with no member pattern");
+ }
+ if (kind == KeepItemKind.CLASS_AND_MEMBERS && memberPattern.isNone()) {
+ throw new KeepEdgeException(
+ "Invalid kind CLASS_AND_MEMBERS for item with no member pattern");
+ }
+ return new KeepItemPattern(kind, classReference, extendsPattern, memberPattern);
}
}
+ private final KeepItemKind kind;
private final KeepClassReference classReference;
private final KeepExtendsPattern extendsPattern;
private final KeepMemberPattern memberPattern;
// TODO: class annotations
private KeepItemPattern(
+ KeepItemKind kind,
KeepClassReference classReference,
KeepExtendsPattern extendsPattern,
KeepMemberPattern memberPattern) {
+ assert kind != null;
assert classReference != null;
assert extendsPattern != null;
assert memberPattern != null;
+ this.kind = kind;
this.classReference = classReference;
this.extendsPattern = extendsPattern;
this.memberPattern = memberPattern;
}
public boolean isAny(Predicate<String> onReference) {
- return extendsPattern.isAny() && memberPattern.isAll() && classReference.isAny(onReference);
+ return kind.equals(KeepItemKind.CLASS_AND_MEMBERS)
+ && extendsPattern.isAny()
+ && memberPattern.isAllMembers()
+ && classReference.isAny(onReference);
+ }
+
+ public KeepItemKind getKind() {
+ return kind;
}
public KeepClassReference getClassReference() {
@@ -127,20 +164,23 @@
return false;
}
KeepItemPattern that = (KeepItemPattern) obj;
- return classReference.equals(that.classReference)
+ return kind.equals(that.kind)
+ && classReference.equals(that.classReference)
&& extendsPattern.equals(that.extendsPattern)
&& memberPattern.equals(that.memberPattern);
}
@Override
public int hashCode() {
- return Objects.hash(classReference, extendsPattern, memberPattern);
+ return Objects.hash(kind, classReference, extendsPattern, memberPattern);
}
@Override
public String toString() {
return "KeepClassPattern{"
- + "classReference="
+ + "kind="
+ + kind
+ + ", classReference="
+ classReference
+ ", extendsPattern="
+ extendsPattern
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
index b0f205e..d994d84 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -9,7 +9,7 @@
return None.getInstance();
}
- public static KeepMemberPattern all() {
+ public static KeepMemberPattern allMembers() {
return All.getInstance();
}
@@ -22,7 +22,7 @@
}
@Override
- public boolean isAll() {
+ public boolean isAllMembers() {
return true;
}
@@ -73,7 +73,7 @@
KeepMemberPattern() {}
- public boolean isAll() {
+ public boolean isAllMembers() {
return false;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
index f84a4b1..4104392 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -12,10 +12,14 @@
return new Builder();
}
+ public static KeepMemberPattern allMethods() {
+ return builder().build();
+ }
+
public static class Builder {
private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.any();
- private KeepMethodNamePattern namePattern = null;
+ private KeepMethodNamePattern namePattern = KeepMethodNamePattern.any();
private KeepMethodReturnTypePattern returnTypePattern = KeepMethodReturnTypePattern.any();
private KeepMethodParametersPattern parametersPattern = KeepMethodParametersPattern.any();
@@ -50,9 +54,6 @@
}
public KeepMethodPattern build() {
- if (namePattern == null) {
- throw new KeepEdgeException("Method pattern must declar a name pattern");
- }
KeepMethodReturnTypePattern returnTypePattern = this.returnTypePattern;
KeepMethodNameExactPattern exactName = namePattern.asExact();
if (exactName != null
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
index 5922be3..1664218 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepItemKind;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
@@ -156,7 +157,10 @@
private KeepItemPattern getMemberItemPattern(
KeepItemPattern fromPattern, KeepClassReference classReference) {
+ assert fromPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)
+ || fromPattern.getKind().equals(KeepItemKind.CLASS_AND_MEMBERS);
return KeepItemPattern.builder()
+ .setKind(fromPattern.getKind())
.setClassReference(classReference)
.setMemberPattern(fromPattern.getMemberPattern())
.build();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeSplitter.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeSplitter.java
deleted file mode 100644
index f565635..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeSplitter.java
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright (c) 2023, 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.keeprules;
-
-import com.android.tools.r8.keepanno.ast.KeepBindings;
-import com.android.tools.r8.keepanno.ast.KeepCondition;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepEdgeException;
-import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepItemReference;
-import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
-import com.android.tools.r8.keepanno.ast.KeepOptions;
-import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepTarget;
-import com.android.tools.r8.keepanno.keeprules.PgRule.PgConditionalClassRule;
-import com.android.tools.r8.keepanno.keeprules.PgRule.PgConditionalMemberRule;
-import com.android.tools.r8.keepanno.keeprules.PgRule.PgDependentClassRule;
-import com.android.tools.r8.keepanno.keeprules.PgRule.PgDependentMembersRule;
-import com.android.tools.r8.keepanno.keeprules.PgRule.PgUnconditionalClassRule;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.BiConsumer;
-
-/** Split a keep edge into multiple PG rules that over-approximate it. */
-public class KeepEdgeSplitter {
-
- public static Collection<PgRule> split(KeepEdge edge) {
- return doSplit(KeepEdgeNormalizer.normalize(edge));
- }
-
- /**
- * Utility to package up a class binding with its name and item pattern.
- *
- * <p>This is useful as the normalizer will have introduced class reference indirections so a
- * given item may need to.
- */
- public static class Holder {
- final KeepItemPattern itemPattern;
- final KeepQualifiedClassNamePattern namePattern;
-
- static Holder create(String bindingName, KeepBindings bindings) {
- KeepItemPattern itemPattern = bindings.get(bindingName).getItem();
- assert itemPattern.isClassItemPattern();
- KeepQualifiedClassNamePattern namePattern = getClassNamePattern(itemPattern, bindings);
- return new Holder(itemPattern, namePattern);
- }
-
- private Holder(KeepItemPattern itemPattern, KeepQualifiedClassNamePattern namePattern) {
- this.itemPattern = itemPattern;
- this.namePattern = namePattern;
- }
- }
-
- private static class BindingUsers {
-
- final Holder holder;
- final Set<String> conditionRefs = new HashSet<>();
- final Map<KeepOptions, Set<String>> targetRefs = new HashMap<>();
-
- static BindingUsers create(String bindingName, KeepBindings bindings) {
- return new BindingUsers(Holder.create(bindingName, bindings));
- }
-
- private BindingUsers(Holder holder) {
- this.holder = holder;
- }
-
- public void addCondition(KeepCondition condition) {
- assert condition.getItem().isBindingReference();
- conditionRefs.add(condition.getItem().asBindingReference());
- }
-
- public void addTarget(KeepTarget target) {
- assert target.getItem().isBindingReference();
- targetRefs
- .computeIfAbsent(target.getOptions(), k -> new HashSet<>())
- .add(target.getItem().asBindingReference());
- }
- }
-
- private static Collection<PgRule> doSplit(KeepEdge edge) {
- List<PgRule> rules = new ArrayList<>();
-
- // First step after normalizing is to group up all conditions and targets on their target class.
- // Here we use the normalized binding as the notion of identity on a class.
- KeepBindings bindings = edge.getBindings();
- Map<String, BindingUsers> bindingUsers = new HashMap<>();
- edge.getPreconditions()
- .forEach(
- condition -> {
- String classReference = getClassItemBindingReference(condition.getItem(), bindings);
- assert classReference != null;
- bindingUsers
- .computeIfAbsent(classReference, k -> BindingUsers.create(k, bindings))
- .addCondition(condition);
- });
- edge.getConsequences()
- .forEachTarget(
- target -> {
- String classReference = getClassItemBindingReference(target.getItem(), bindings);
- assert classReference != null;
- bindingUsers
- .computeIfAbsent(classReference, k -> BindingUsers.create(k, bindings))
- .addTarget(target);
- });
-
- bindingUsers.forEach(
- (targetBindingName, users) -> {
- Holder targetHolder = users.holder;
- if (!users.conditionRefs.isEmpty() && !users.targetRefs.isEmpty()) {
- // The targets depend on the condition and thus we generate just the dependent edges.
- users.targetRefs.forEach(
- (options, targets) -> {
- createDependentRules(
- rules,
- targetHolder,
- edge.getMetaInfo(),
- bindings,
- options,
- users.conditionRefs,
- targets);
- });
- } else if (!users.targetRefs.isEmpty()) {
- // The targets don't have a binding relation to any conditions, so we generate a rule
- // per condition, or a single unconditional edge if no conditions exist.
- if (edge.getPreconditions().isAlways()) {
- users.targetRefs.forEach(
- ((options, targets) -> {
- createUnconditionalRules(
- rules, targetHolder, edge.getMetaInfo(), bindings, options, targets);
- }));
- } else {
- users.targetRefs.forEach(
- ((options, targets) -> {
- // Note that here we iterate over *all* non-empty conditions and create rules.
- bindingUsers.forEach(
- (conditionBindingName, conditionUsers) -> {
- if (!conditionUsers.conditionRefs.isEmpty()) {
- createConditionalRules(
- rules,
- edge.getMetaInfo(),
- conditionUsers.holder,
- targetHolder,
- bindings,
- options,
- conditionUsers.conditionRefs,
- targets);
- }
- });
- }));
- }
- }
- });
-
- assert !rules.isEmpty();
- return rules;
- }
-
- private static List<String> computeConditions(
- Set<String> conditions,
- KeepBindings bindings,
- Map<String, KeepMemberPattern> memberPatterns) {
- List<String> conditionMembers = new ArrayList<>();
- conditions.forEach(
- conditionReference -> {
- KeepItemPattern item = bindings.get(conditionReference).getItem();
- if (item.isMemberItemPattern()) {
- KeepMemberPattern old = memberPatterns.put(conditionReference, item.getMemberPattern());
- conditionMembers.add(conditionReference);
- assert old == null;
- }
- });
- return conditionMembers;
- }
-
- private static void computeTargets(
- Set<String> targets,
- KeepBindings bindings,
- Map<String, KeepMemberPattern> memberPatterns,
- Runnable onKeepClass,
- BiConsumer<Map<String, KeepMemberPattern>, List<String>> onKeepMembers) {
- List<String> targetMembers = new ArrayList<>();
- boolean keepClassTarget = false;
- for (String targetReference : targets) {
- KeepItemPattern item = bindings.get(targetReference).getItem();
- if (item.isClassItemPattern() || bindings.isAny(item)) {
- keepClassTarget = true;
- }
- if (item.isMemberItemPattern()) {
- memberPatterns.putIfAbsent(targetReference, item.getMemberPattern());
- targetMembers.add(targetReference);
- }
- }
- if (keepClassTarget) {
- onKeepClass.run();
- }
- if (!targetMembers.isEmpty()) {
- onKeepMembers.accept(memberPatterns, targetMembers);
- }
- }
-
- private static void createUnconditionalRules(
- List<PgRule> rules,
- Holder holder,
- KeepEdgeMetaInfo metaInfo,
- KeepBindings bindings,
- KeepOptions options,
- Set<String> targets) {
- computeTargets(
- targets,
- bindings,
- new HashMap<>(),
- () -> {
- rules.add(new PgUnconditionalClassRule(metaInfo, options, holder));
- },
- (memberPatterns, targetMembers) -> {
- // Members are still dependent on the class, so they go to the implicitly dependent rule.
- rules.add(
- new PgDependentMembersRule(
- metaInfo,
- holder,
- options,
- memberPatterns,
- Collections.emptyList(),
- targetMembers));
- });
- }
-
- private static void createConditionalRules(
- List<PgRule> rules,
- KeepEdgeMetaInfo metaInfo,
- Holder conditionHolder,
- Holder targetHolder,
- KeepBindings bindings,
- KeepOptions options,
- Set<String> conditions,
- Set<String> targets) {
-
- Map<String, KeepMemberPattern> memberPatterns = new HashMap<>();
- List<String> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
-
- computeTargets(
- targets,
- bindings,
- memberPatterns,
- () ->
- rules.add(
- new PgConditionalClassRule(
- metaInfo,
- options,
- conditionHolder,
- targetHolder,
- memberPatterns,
- conditionMembers)),
- (ignore, targetMembers) ->
- rules.add(
- new PgConditionalMemberRule(
- metaInfo,
- options,
- conditionHolder,
- targetHolder,
- memberPatterns,
- conditionMembers,
- targetMembers)));
- }
-
- // For a conditional and dependent edge (e.g., the condition and target both reference holder X),
- // we can assume the general form of:
- //
- // { X, memberConds } -> { X, memberTargets }
- //
- // First, we assume that if memberConds=={} then X is in the conditions, otherwise the conditions
- // are empty (i.e. always true) and this is not a dependent edge.
- //
- // Without change in meaning we can always assume X in conditions as it either was and if not then
- // the condition on a member implicitly entails a condition on the holder.
- //
- // Next we can split any such edge into two edges:
- //
- // { X, memberConds } -> { X }
- // { X, memberConds } -> { memberTargets }
- //
- // The first edge, if present, gives rise to the rule:
- //
- // -if class X { memberConds } -keep class <1>
- //
- // The second rule only pertains to keeping member targets and those targets are kept as a
- // -keepclassmembers such that they are still conditional on the holder being referenced/live.
- // If the only precondition is the holder, then it can omitted, thus we generate:
- // If memberConds={}:
- // -keepclassmembers class X { memberTargets }
- // else:
- // -if class X { memberConds } -keepclassmembers X { memberTargets }
- //
- private static void createDependentRules(
- List<PgRule> rules,
- Holder holder,
- KeepEdgeMetaInfo metaInfo,
- KeepBindings bindings,
- KeepOptions options,
- Set<String> conditions,
- Set<String> targets) {
- Map<String, KeepMemberPattern> memberPatterns = new HashMap<>();
- List<String> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
- computeTargets(
- targets,
- bindings,
- memberPatterns,
- () ->
- rules.add(
- new PgDependentClassRule(
- metaInfo, holder, options, memberPatterns, conditionMembers)),
- (ignore, targetMembers) ->
- rules.add(
- new PgDependentMembersRule(
- metaInfo, holder, options, memberPatterns, conditionMembers, targetMembers)));
- }
-
- private static KeepQualifiedClassNamePattern getClassNamePattern(
- KeepItemPattern itemPattern, KeepBindings bindings) {
- return itemPattern.getClassReference().isClassNamePattern()
- ? itemPattern.getClassReference().asClassNamePattern()
- : getClassNamePattern(
- bindings.get(itemPattern.getClassReference().asBindingReference()).getItem(), bindings);
- }
-
- private static String getClassItemBindingReference(
- KeepItemReference itemReference, KeepBindings bindings) {
- String classReference = null;
- for (String reference : getTransitiveBindingReferences(itemReference, bindings)) {
- if (bindings.get(reference).getItem().isClassItemPattern()) {
- if (classReference != null) {
- throw new KeepEdgeException("Unexpected reference to multiple class bindings");
- }
- classReference = reference;
- }
- }
- return classReference;
- }
-
- private static Set<String> getTransitiveBindingReferences(
- KeepItemReference itemReference, KeepBindings bindings) {
- Set<String> references = new HashSet<>(2);
- Deque<String> worklist = new ArrayDeque<>();
- worklist.addAll(getBindingReference(itemReference));
- while (!worklist.isEmpty()) {
- String bindingReference = worklist.pop();
- if (references.add(bindingReference)) {
- worklist.addAll(getBindingReference(bindings.get(bindingReference).getItem()));
- }
- }
- return references;
- }
-
- private static Collection<String> getBindingReference(KeepItemReference itemReference) {
- if (itemReference.isBindingReference()) {
- return Collections.singletonList(itemReference.asBindingReference());
- }
- return getBindingReference(itemReference.asItemPattern());
- }
-
- private static Collection<String> getBindingReference(KeepItemPattern itemPattern) {
- return itemPattern.getClassReference().isBindingReference()
- ? Collections.singletonList(itemPattern.getClassReference().asBindingReference())
- : Collections.emptyList();
- }
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 39ae46d..ee80978 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -1,36 +1,38 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2023, 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.keeprules;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepBindings;
+import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
-import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
-import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
-import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
-import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
import com.android.tools.r8.keepanno.ast.KeepOptions;
-import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
-import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepTypePattern;
-import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
-import com.android.tools.r8.keepanno.utils.Unimplemented;
+import com.android.tools.r8.keepanno.ast.KeepTarget;
+import com.android.tools.r8.keepanno.keeprules.PgRule.PgConditionalRule;
+import com.android.tools.r8.keepanno.keeprules.PgRule.PgDependentMembersRule;
+import com.android.tools.r8.keepanno.keeprules.PgRule.PgUnconditionalRule;
+import com.android.tools.r8.keepanno.keeprules.PgRule.TargetKeepKind;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.function.BiConsumer;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
-/** Extract out a sequence of Proguard keep rules that give a conservative over-approximation. */
+/** Extract the PG keep rules that over-approximate a keep edge. */
public class KeepRuleExtractor {
private final Consumer<String> ruleConsumer;
@@ -40,7 +42,7 @@
}
public void extract(KeepEdge edge) {
- Collection<PgRule> rules = KeepEdgeSplitter.split(edge);
+ Collection<PgRule> rules = split(edge);
StringBuilder builder = new StringBuilder();
for (PgRule rule : rules) {
rule.printRule(builder);
@@ -49,273 +51,395 @@
ruleConsumer.accept(builder.toString());
}
- public static void printHeader(StringBuilder builder, KeepEdgeMetaInfo metaInfo) {
- if (metaInfo.hasContext()) {
- builder.append("# context: ").append(metaInfo.getContextDescriptorString()).append('\n');
+ private static Collection<PgRule> split(KeepEdge edge) {
+ return doSplit(KeepEdgeNormalizer.normalize(edge));
+ }
+
+ /**
+ * Utility to package up a class binding with its name and item pattern.
+ *
+ * <p>This is useful as the normalizer will have introduced class reference indirections so a
+ * given item may need to.
+ */
+ public static class Holder {
+ final KeepItemPattern itemPattern;
+ final KeepQualifiedClassNamePattern namePattern;
+
+ static Holder create(String bindingName, KeepBindings bindings) {
+ KeepItemPattern itemPattern = bindings.get(bindingName).getItem();
+ assert itemPattern.isClassItemPattern();
+ KeepQualifiedClassNamePattern namePattern = getClassNamePattern(itemPattern, bindings);
+ return new Holder(itemPattern, namePattern);
}
- if (metaInfo.hasDescription()) {
- String escapedDescription = escapeLineBreaks(metaInfo.getDescriptionString());
- builder.append("# description: ").append(escapedDescription).append('\n');
+
+ private Holder(KeepItemPattern itemPattern, KeepQualifiedClassNamePattern namePattern) {
+ this.itemPattern = itemPattern;
+ this.namePattern = namePattern;
}
}
- public static String escapeChar(char c) {
- if (c == '\n') {
- return "\\n";
+ private static class BindingUsers {
+
+ final Holder holder;
+ final Set<String> conditionRefs = new HashSet<>();
+ final Map<KeepOptions, Set<String>> targetRefs = new HashMap<>();
+
+ static BindingUsers create(String bindingName, KeepBindings bindings) {
+ return new BindingUsers(Holder.create(bindingName, bindings));
}
- if (c == '\r') {
- return "\\r";
+
+ private BindingUsers(Holder holder) {
+ this.holder = holder;
}
- return null;
+
+ public void addCondition(KeepCondition condition) {
+ assert condition.getItem().isBindingReference();
+ conditionRefs.add(condition.getItem().asBindingReference());
+ }
+
+ public void addTarget(KeepTarget target) {
+ assert target.getItem().isBindingReference();
+ targetRefs
+ .computeIfAbsent(target.getOptions(), k -> new HashSet<>())
+ .add(target.getItem().asBindingReference());
+ }
}
- public static String escapeLineBreaks(String string) {
- char[] charArray = string.toCharArray();
- for (int i = 0; i < charArray.length; i++) {
- // We don't expect escape chars, so wait with constructing a new string until found.
- if (escapeChar(charArray[i]) != null) {
- StringBuilder builder = new StringBuilder(string.substring(0, i));
- for (int j = i; j < charArray.length; j++) {
- char c = charArray[j];
- String escaped = escapeChar(c);
- if (escaped != null) {
- builder.append(escaped);
- } else {
- builder.append(c);
+ private static Collection<PgRule> doSplit(KeepEdge edge) {
+ List<PgRule> rules = new ArrayList<>();
+
+ // First step after normalizing is to group up all conditions and targets on their target class.
+ // Here we use the normalized binding as the notion of identity on a class.
+ KeepBindings bindings = edge.getBindings();
+ Map<String, BindingUsers> bindingUsers = new HashMap<>();
+ edge.getPreconditions()
+ .forEach(
+ condition -> {
+ String classReference = getClassItemBindingReference(condition.getItem(), bindings);
+ assert classReference != null;
+ bindingUsers
+ .computeIfAbsent(classReference, k -> BindingUsers.create(k, bindings))
+ .addCondition(condition);
+ });
+ edge.getConsequences()
+ .forEachTarget(
+ target -> {
+ String classReference = getClassItemBindingReference(target.getItem(), bindings);
+ assert classReference != null;
+ bindingUsers
+ .computeIfAbsent(classReference, k -> BindingUsers.create(k, bindings))
+ .addTarget(target);
+ });
+
+ bindingUsers.forEach(
+ (targetBindingName, users) -> {
+ Holder targetHolder = users.holder;
+ if (!users.conditionRefs.isEmpty() && !users.targetRefs.isEmpty()) {
+ // The targets depend on the condition and thus we generate just the dependent edges.
+ users.targetRefs.forEach(
+ (options, targets) -> {
+ createDependentRules(
+ rules,
+ targetHolder,
+ edge.getMetaInfo(),
+ bindings,
+ options,
+ users.conditionRefs,
+ targets);
+ });
+ } else if (!users.targetRefs.isEmpty()) {
+ // The targets don't have a binding relation to any conditions, so we generate a rule
+ // per condition, or a single unconditional edge if no conditions exist.
+ if (edge.getPreconditions().isAlways()) {
+ users.targetRefs.forEach(
+ ((options, targets) -> {
+ createUnconditionalRules(
+ rules, targetHolder, edge.getMetaInfo(), bindings, options, targets);
+ }));
+ } else {
+ users.targetRefs.forEach(
+ ((options, targets) -> {
+ // Note that here we iterate over *all* non-empty conditions and create rules.
+ // Doing so over-approximates the matching instances of the edge, but gives
+ // better stability of the extraction as it avoids picking a particular
+ // precondition as the "primary" one to act on.
+ bindingUsers.forEach(
+ (conditionBindingName, conditionUsers) -> {
+ if (!conditionUsers.conditionRefs.isEmpty()) {
+ createConditionalRules(
+ rules,
+ edge.getMetaInfo(),
+ conditionUsers.holder,
+ targetHolder,
+ bindings,
+ options,
+ conditionUsers.conditionRefs,
+ targets);
+ }
+ });
+ }));
+ }
}
+ });
+
+ assert !rules.isEmpty();
+ return rules;
+ }
+
+ private static List<String> computeConditions(
+ Set<String> conditions,
+ KeepBindings bindings,
+ Map<String, KeepMemberPattern> memberPatterns) {
+ List<String> conditionMembers = new ArrayList<>();
+ conditions.forEach(
+ conditionReference -> {
+ KeepItemPattern item = bindings.get(conditionReference).getItem();
+ if (!item.isClassItemPattern()) {
+ KeepMemberPattern old = memberPatterns.put(conditionReference, item.getMemberPattern());
+ conditionMembers.add(conditionReference);
+ assert old == null;
+ }
+ });
+ return conditionMembers;
+ }
+
+ @FunctionalInterface
+ private interface OnTargetCallback {
+ void accept(
+ Map<String, KeepMemberPattern> memberPatterns,
+ List<String> memberTargets,
+ TargetKeepKind keepKind);
+ }
+
+ private static void computeTargets(
+ Set<String> targets,
+ KeepBindings bindings,
+ Map<String, KeepMemberPattern> memberPatterns,
+ OnTargetCallback callback) {
+ boolean keepClassTarget = false;
+ List<String> disjunctiveTargetMembers = new ArrayList<>();
+ List<String> classConjunctiveTargetMembers = new ArrayList<>();
+
+ for (String targetReference : targets) {
+ KeepItemPattern item = bindings.get(targetReference).getItem();
+ if (bindings.isAny(item)) {
+ // If the target is "any item" then it contains any other target pattern.
+ memberPatterns.put(targetReference, item.getMemberPattern());
+ callback.accept(
+ memberPatterns,
+ Collections.singletonList(targetReference),
+ TargetKeepKind.CLASS_OR_MEMBERS);
+ return;
+ }
+ if (item.isClassItemPattern()) {
+ keepClassTarget = true;
+ } else {
+ memberPatterns.putIfAbsent(targetReference, item.getMemberPattern());
+ if (item.isClassAndMemberPattern()) {
+ // If a target is a "class and member" target then it must be added as a separate rule.
+ classConjunctiveTargetMembers.add(targetReference);
+ } else {
+ assert item.isMemberItemPattern();
+ disjunctiveTargetMembers.add(targetReference);
}
- return builder.toString();
}
}
- return string;
- }
- public static void printKeepOptions(StringBuilder builder, KeepOptions options) {
- for (KeepOption option : KeepOption.values()) {
- if (options.isAllowed(option)) {
- builder.append(",allow").append(getOptionString(option));
+ // The class is targeted, so that part of a class-and-member conjunction is satisfied.
+ // The conjunctive members can thus be moved to the disjunctive set.
+ if (keepClassTarget) {
+ disjunctiveTargetMembers.addAll(classConjunctiveTargetMembers);
+ classConjunctiveTargetMembers.clear();
+ }
+
+ if (!disjunctiveTargetMembers.isEmpty()) {
+ TargetKeepKind keepKind =
+ keepClassTarget ? TargetKeepKind.CLASS_OR_MEMBERS : TargetKeepKind.JUST_MEMBERS;
+ callback.accept(memberPatterns, disjunctiveTargetMembers, keepKind);
+ } else if (keepClassTarget) {
+ callback.accept(
+ Collections.emptyMap(), Collections.emptyList(), TargetKeepKind.CLASS_OR_MEMBERS);
+ }
+
+ if (!classConjunctiveTargetMembers.isEmpty()) {
+ assert !keepClassTarget;
+ for (String targetReference : classConjunctiveTargetMembers) {
+ callback.accept(
+ memberPatterns,
+ Collections.singletonList(targetReference),
+ TargetKeepKind.CLASS_AND_MEMBERS);
}
}
}
- public static StringBuilder printClassHeader(
- StringBuilder builder,
- KeepItemPattern classPattern,
- BiConsumer<StringBuilder, KeepClassReference> printClassReference) {
- builder.append("class ");
- printClassReference.accept(builder, classPattern.getClassReference());
- KeepExtendsPattern extendsPattern = classPattern.getExtendsPattern();
- if (!extendsPattern.isAny()) {
- builder.append(" extends ");
- printClassName(builder, extendsPattern.asClassNamePattern());
- }
- return builder;
+ private static void createUnconditionalRules(
+ List<PgRule> rules,
+ Holder holder,
+ KeepEdgeMetaInfo metaInfo,
+ KeepBindings bindings,
+ KeepOptions options,
+ Set<String> targets) {
+ computeTargets(
+ targets,
+ bindings,
+ new HashMap<>(),
+ (memberPatterns, targetMembers, targetKeepKind) -> {
+ if (targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS)) {
+ // Members dependent on the class, so they go to the implicitly dependent rule.
+ rules.add(
+ new PgDependentMembersRule(
+ metaInfo,
+ holder,
+ options,
+ memberPatterns,
+ Collections.emptyList(),
+ targetMembers,
+ targetKeepKind));
+ } else {
+ rules.add(
+ new PgUnconditionalRule(
+ metaInfo, holder, options, memberPatterns, targetMembers, targetKeepKind));
+ }
+ });
}
- public static StringBuilder printMemberClause(StringBuilder builder, KeepMemberPattern member) {
- if (member.isAll()) {
- return builder.append("*;");
- }
- if (member.isMethod()) {
- return printMethod(builder, member.asMethod());
- }
- if (member.isField()) {
- return printField(builder, member.asField());
- }
- throw new Unimplemented();
+ private static void createConditionalRules(
+ List<PgRule> rules,
+ KeepEdgeMetaInfo metaInfo,
+ Holder conditionHolder,
+ Holder targetHolder,
+ KeepBindings bindings,
+ KeepOptions options,
+ Set<String> conditions,
+ Set<String> targets) {
+ Map<String, KeepMemberPattern> memberPatterns = new HashMap<>();
+ List<String> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
+ computeTargets(
+ targets,
+ bindings,
+ memberPatterns,
+ (ignore, targetMembers, targetKeepKind) ->
+ rules.add(
+ new PgConditionalRule(
+ metaInfo,
+ options,
+ conditionHolder,
+ targetHolder,
+ memberPatterns,
+ conditionMembers,
+ targetMembers,
+ targetKeepKind)));
}
- private static StringBuilder printField(StringBuilder builder, KeepFieldPattern fieldPattern) {
- if (fieldPattern.isAnyField()) {
- return builder.append("<fields>;");
- }
- printAccess(builder, " ", fieldPattern.getAccessPattern());
- printType(builder, fieldPattern.getTypePattern().asType());
- builder.append(' ');
- printFieldName(builder, fieldPattern.getNamePattern());
- return builder.append(';');
+ private static void createDependentRules(
+ List<PgRule> rules,
+ Holder holder,
+ KeepEdgeMetaInfo metaInfo,
+ KeepBindings bindings,
+ KeepOptions options,
+ Set<String> conditions,
+ Set<String> targets) {
+ Map<String, KeepMemberPattern> memberPatterns = new HashMap<>();
+ List<String> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
+ computeTargets(
+ targets,
+ bindings,
+ memberPatterns,
+ (ignore, targetMembers, targetKeepKind) -> {
+ List<String> nonAllMemberTargets = new ArrayList<>(targetMembers.size());
+ for (String targetMember : targetMembers) {
+ KeepMemberPattern memberPattern = memberPatterns.get(targetMember);
+ if (memberPattern.isAllMembers() && conditionMembers.contains(targetMember)) {
+ // This pattern is a pattern for "any member" and it is bound by a condition.
+ // Since backrefs can't reference a *-member we split this target in two, one for
+ // fields and one for methods.
+ HashMap<String, KeepMemberPattern> copyWithMethod = new HashMap<>(memberPatterns);
+ copyWithMethod.put(targetMember, KeepMethodPattern.allMethods());
+ rules.add(
+ new PgDependentMembersRule(
+ metaInfo,
+ holder,
+ options,
+ copyWithMethod,
+ conditionMembers,
+ Collections.singletonList(targetMember),
+ targetKeepKind));
+ HashMap<String, KeepMemberPattern> copyWithField = new HashMap<>(memberPatterns);
+ copyWithField.put(targetMember, KeepFieldPattern.allFields());
+ rules.add(
+ new PgDependentMembersRule(
+ metaInfo,
+ holder,
+ options,
+ copyWithField,
+ conditionMembers,
+ Collections.singletonList(targetMember),
+ targetKeepKind));
+ } else {
+ nonAllMemberTargets.add(targetMember);
+ }
+ }
+ if (targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS) && nonAllMemberTargets.isEmpty()) {
+ return;
+ }
+ rules.add(
+ new PgDependentMembersRule(
+ metaInfo,
+ holder,
+ options,
+ memberPatterns,
+ conditionMembers,
+ nonAllMemberTargets,
+ targetKeepKind));
+ });
}
- private static StringBuilder printMethod(StringBuilder builder, KeepMethodPattern methodPattern) {
- if (methodPattern.isAnyMethod()) {
- return builder.append("<methods>;");
- }
- printAccess(builder, " ", methodPattern.getAccessPattern());
- printReturnType(builder, methodPattern.getReturnTypePattern());
- builder.append(' ');
- printMethodName(builder, methodPattern.getNamePattern());
- printParameters(builder, methodPattern.getParametersPattern());
- return builder.append(';');
+ private static KeepQualifiedClassNamePattern getClassNamePattern(
+ KeepItemPattern itemPattern, KeepBindings bindings) {
+ return itemPattern.getClassReference().isClassNamePattern()
+ ? itemPattern.getClassReference().asClassNamePattern()
+ : getClassNamePattern(
+ bindings.get(itemPattern.getClassReference().asBindingReference()).getItem(), bindings);
}
- private static StringBuilder printParameters(
- StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
- if (parametersPattern.isAny()) {
- return builder.append("(...)");
- }
- return builder
- .append('(')
- .append(
- parametersPattern.asList().stream()
- .map(KeepRuleExtractor::getTypePatternString)
- .collect(Collectors.joining(", ")))
- .append(')');
- }
-
- private static StringBuilder printFieldName(
- StringBuilder builder, KeepFieldNamePattern namePattern) {
- return namePattern.isAny()
- ? builder.append("*")
- : builder.append(namePattern.asExact().getName());
- }
-
- private static StringBuilder printMethodName(
- StringBuilder builder, KeepMethodNamePattern namePattern) {
- return namePattern.isAny()
- ? builder.append("*")
- : builder.append(namePattern.asExact().getName());
- }
-
- private static StringBuilder printReturnType(
- StringBuilder builder, KeepMethodReturnTypePattern returnTypePattern) {
- if (returnTypePattern.isVoid()) {
- return builder.append("void");
- }
- return printType(builder, returnTypePattern.asType());
- }
-
- private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
- return builder.append(getTypePatternString(typePattern));
- }
-
- private static StringBuilder printAccess(
- StringBuilder builder, String indent, KeepMethodAccessPattern accessPattern) {
- if (accessPattern.isAny()) {
- // No text will match any access pattern.
- // Don't print the indent in this case.
- return builder;
- }
- throw new Unimplemented();
- }
-
- private static StringBuilder printAccess(
- StringBuilder builder, String indent, KeepFieldAccessPattern accessPattern) {
- if (accessPattern.isAny()) {
- // No text will match any access pattern.
- // Don't print the indent in this case.
- return builder;
- }
- throw new Unimplemented();
- }
-
- public static StringBuilder printClassName(
- StringBuilder builder, KeepQualifiedClassNamePattern classNamePattern) {
- if (classNamePattern.isAny()) {
- return builder.append('*');
- }
- printPackagePrefix(builder, classNamePattern.getPackagePattern());
- return printSimpleClassName(builder, classNamePattern.getNamePattern());
- }
-
- private static StringBuilder printPackagePrefix(
- StringBuilder builder, KeepPackagePattern packagePattern) {
- if (packagePattern.isAny()) {
- return builder.append("**.");
- }
- if (packagePattern.isTop()) {
- return builder;
- }
- assert packagePattern.isExact();
- return builder.append(packagePattern.getExactPackageAsString()).append('.');
- }
-
- private static StringBuilder printSimpleClassName(
- StringBuilder builder, KeepUnqualfiedClassNamePattern namePattern) {
- if (namePattern.isAny()) {
- return builder.append('*');
- }
- assert namePattern.isExact();
- return builder.append(namePattern.asExact().getExactNameAsString());
- }
-
- private static String getOptionString(KeepOption option) {
- switch (option) {
- case SHRINKING:
- return "shrinking";
- case OPTIMIZING:
- return "optimization";
- case OBFUSCATING:
- return "obfuscation";
- case ACCESS_MODIFICATION:
- return "accessmodification";
- case ANNOTATION_REMOVAL:
- return "annotationremoval";
- default:
- throw new Unimplemented();
- }
- }
-
- private static String getTypePatternString(KeepTypePattern typePattern) {
- if (typePattern.isAny()) {
- return "***";
- }
- return descriptorToJavaType(typePattern.getDescriptor());
- }
-
- private static String descriptorToJavaType(String descriptor) {
- if (descriptor.isEmpty()) {
- throw new KeepEdgeException("Invalid empty type descriptor");
- }
- if (descriptor.length() == 1) {
- return primitiveDescriptorToJavaType(descriptor.charAt(0));
- }
- if (descriptor.charAt(0) == '[') {
- return arrayDescriptorToJavaType(descriptor);
- }
- return classDescriptorToJavaType(descriptor);
- }
-
- private static String primitiveDescriptorToJavaType(char descriptor) {
- switch (descriptor) {
- case 'Z':
- return "boolean";
- case 'B':
- return "byte";
- case 'S':
- return "short";
- case 'I':
- return "int";
- case 'J':
- return "long";
- case 'F':
- return "float";
- case 'D':
- return "double";
- default:
- throw new KeepEdgeException("Invalid primitive descriptor: " + descriptor);
- }
- }
-
- private static String classDescriptorToJavaType(String descriptor) {
- int last = descriptor.length() - 1;
- if (descriptor.charAt(0) != 'L' || descriptor.charAt(last) != ';') {
- throw new KeepEdgeException("Invalid class descriptor: " + descriptor);
- }
- return descriptor.substring(1, last).replace('/', '.');
- }
-
- private static String arrayDescriptorToJavaType(String descriptor) {
- for (int i = 0; i < descriptor.length(); i++) {
- char c = descriptor.charAt(i);
- if (c != '[') {
- StringBuilder builder = new StringBuilder();
- builder.append(descriptorToJavaType(descriptor.substring(i)));
- for (int j = 0; j < i; j++) {
- builder.append("[]");
+ private static String getClassItemBindingReference(
+ KeepItemReference itemReference, KeepBindings bindings) {
+ String classReference = null;
+ for (String reference : getTransitiveBindingReferences(itemReference, bindings)) {
+ if (bindings.get(reference).getItem().isClassItemPattern()) {
+ if (classReference != null) {
+ throw new KeepEdgeException("Unexpected reference to multiple class bindings");
}
- return builder.toString();
+ classReference = reference;
}
}
- throw new KeepEdgeException("Invalid array descriptor: " + descriptor);
+ return classReference;
+ }
+
+ private static Set<String> getTransitiveBindingReferences(
+ KeepItemReference itemReference, KeepBindings bindings) {
+ Set<String> references = new HashSet<>(2);
+ Deque<String> worklist = new ArrayDeque<>();
+ worklist.addAll(getBindingReference(itemReference));
+ while (!worklist.isEmpty()) {
+ String bindingReference = worklist.pop();
+ if (references.add(bindingReference)) {
+ worklist.addAll(getBindingReference(bindings.get(bindingReference).getItem()));
+ }
+ }
+ return references;
+ }
+
+ private static Collection<String> getBindingReference(KeepItemReference itemReference) {
+ if (itemReference.isBindingReference()) {
+ return Collections.singletonList(itemReference.asBindingReference());
+ }
+ return getBindingReference(itemReference.asItemPattern());
+ }
+
+ private static Collection<String> getBindingReference(KeepItemPattern itemPattern) {
+ return itemPattern.getClassReference().isBindingReference()
+ ? Collections.singletonList(itemPattern.getClassReference().asBindingReference())
+ : Collections.emptyList();
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
index 9a9ccc0..412a91e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
@@ -3,22 +3,46 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.keeprules;
+import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printClassHeader;
+import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printMemberClause;
+
import com.android.tools.r8.keepanno.ast.KeepClassReference;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepOptions;
-import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
-import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
-import com.android.tools.r8.keepanno.keeprules.KeepEdgeSplitter.Holder;
-import java.util.Collections;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor.Holder;
+import com.android.tools.r8.keepanno.keeprules.RulePrinter.BackReferencePrinter;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
public abstract class PgRule {
+ public enum TargetKeepKind {
+ JUST_MEMBERS(RulePrintingUtils.KEEP_CLASS_MEMBERS),
+ CLASS_OR_MEMBERS(RulePrintingUtils.KEEP),
+ CLASS_AND_MEMBERS(RulePrintingUtils.KEEP_CLASSES_WITH_MEMBERS);
+
+ private final String ruleKind;
+
+ TargetKeepKind(String ruleKind) {
+ this.ruleKind = ruleKind;
+ }
+
+ String getKeepRuleKind() {
+ return ruleKind;
+ }
+ }
+
+ private static void printNonEmptyMembersPatternAsDefaultInitWorkaround(StringBuilder builder) {
+ // If no members is given, compat R8 and legacy full mode will implicitly keep <init>().
+ // Add a keep of finalize which is a library method that would be kept in any case.
+ builder.append(" { void finalize(); }");
+ }
+
private final KeepEdgeMetaInfo metaInfo;
private final KeepOptions options;
@@ -33,26 +57,27 @@
// without a binding indirection).
public static BiConsumer<StringBuilder, KeepClassReference> classReferencePrinter(
KeepQualifiedClassNamePattern classNamePattern) {
- return (StringBuilder builder, KeepClassReference classReference) -> {
+ return (builder, classReference) -> {
assert classReference.isBindingReference()
|| classReference.asClassNamePattern().equals(classNamePattern);
- KeepRuleExtractor.printClassName(builder, classNamePattern);
+ RulePrintingUtils.printClassName(
+ classNamePattern, RulePrinter.withoutBackReferences(builder));
};
}
void printKeepOptions(StringBuilder builder) {
- KeepRuleExtractor.printKeepOptions(builder, options);
+ RulePrintingUtils.printKeepOptions(builder, options);
}
public void printRule(StringBuilder builder) {
- KeepRuleExtractor.printHeader(builder, metaInfo);
+ RulePrintingUtils.printHeader(builder, metaInfo);
printCondition(builder);
printConsequence(builder);
}
void printCondition(StringBuilder builder) {
if (hasCondition()) {
- builder.append("-if ");
+ builder.append(RulePrintingUtils.IF).append(' ');
printConditionHolder(builder);
List<String> members = getConditionMembers();
if (!members.isEmpty()) {
@@ -86,7 +111,6 @@
boolean hasCondition() {
return false;
}
- ;
List<String> getConditionMembers() {
throw new KeepEdgeException("Unreachable");
@@ -109,64 +133,97 @@
abstract void printTargetMember(StringBuilder builder, String member);
/**
- * Representation of an unconditional rule to keep a class.
+ * Representation of an unconditional rule to keep a class and methods.
*
* <pre>
- * -keep class <holder>
+ * -keep[classeswithmembers] class <holder> [{ <members> }]
* </pre>
*
* and with no dependencies / back-references.
*/
- static class PgUnconditionalClassRule extends PgRule {
- final KeepQualifiedClassNamePattern holderNamePattern;
- final KeepItemPattern holderPattern;
+ static class PgUnconditionalRule extends PgRule {
+ private final KeepQualifiedClassNamePattern holderNamePattern;
+ private final KeepItemPattern holderPattern;
+ private final TargetKeepKind targetKeepKind;
+ private final List<String> targetMembers;
+ private final Map<String, KeepMemberPattern> memberPatterns;
- public PgUnconditionalClassRule(KeepEdgeMetaInfo metaInfo, KeepOptions options, Holder holder) {
+ public PgUnconditionalRule(
+ KeepEdgeMetaInfo metaInfo,
+ Holder holder,
+ KeepOptions options,
+ Map<String, KeepMemberPattern> memberPatterns,
+ List<String> targetMembers,
+ TargetKeepKind targetKeepKind) {
super(metaInfo, options);
+ assert !targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS);
this.holderNamePattern = holder.namePattern;
this.holderPattern = holder.itemPattern;
+ this.targetKeepKind = targetKeepKind;
+ this.memberPatterns = memberPatterns;
+ this.targetMembers = targetMembers;
}
@Override
String getConsequenceKeepType() {
- return "-keep";
+ return targetKeepKind.getKeepRuleKind();
}
@Override
List<String> getTargetMembers() {
- return Collections.emptyList();
+ return targetMembers;
}
@Override
void printTargetHolder(StringBuilder builder) {
- KeepRuleExtractor.printClassHeader(
- builder, holderPattern, classReferencePrinter(holderNamePattern));
+ printClassHeader(builder, holderPattern, classReferencePrinter(holderNamePattern));
+ if (getTargetMembers().isEmpty()) {
+ printNonEmptyMembersPatternAsDefaultInitWorkaround(builder);
+ }
}
@Override
void printTargetMember(StringBuilder builder, String memberReference) {
- throw new KeepEdgeException("Unreachable");
+ KeepMemberPattern memberPattern = memberPatterns.get(memberReference);
+ printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
}
- abstract static class PgConditionalRuleBase extends PgRule {
+ /**
+ * Representation of conditional rules but without dependencies between condition and target.
+ *
+ * <pre>
+ * -if class <class-condition> [{ <member-conditions> }]
+ * -keepX class <class-target> [{ <member-targets> }]
+ * </pre>
+ *
+ * and with no dependencies / back-references.
+ */
+ static class PgConditionalRule extends PgRule {
+
final KeepItemPattern classCondition;
final KeepItemPattern classTarget;
final Map<String, KeepMemberPattern> memberPatterns;
final List<String> memberConditions;
+ private final List<String> memberTargets;
+ private final TargetKeepKind keepKind;
- public PgConditionalRuleBase(
+ public PgConditionalRule(
KeepEdgeMetaInfo metaInfo,
KeepOptions options,
Holder classCondition,
Holder classTarget,
Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions) {
+ List<String> memberConditions,
+ List<String> memberTargets,
+ TargetKeepKind keepKind) {
super(metaInfo, options);
this.classCondition = classCondition.itemPattern;
this.classTarget = classTarget.itemPattern;
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
+ this.memberTargets = memberTargets;
+ this.keepKind = keepKind;
}
@Override
@@ -181,92 +238,26 @@
@Override
void printConditionHolder(StringBuilder builder) {
- KeepRuleExtractor.printClassHeader(builder, classCondition, this::printClassName);
+ printClassHeader(builder, classCondition, this::printClassName);
}
@Override
void printConditionMember(StringBuilder builder, String member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
- KeepRuleExtractor.printMemberClause(builder, memberPattern);
+ printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@Override
void printTargetHolder(StringBuilder builder) {
- KeepRuleExtractor.printClassHeader(builder, classTarget, this::printClassName);
- }
-
- void printClassName(StringBuilder builder, KeepClassReference clazz) {
- KeepRuleExtractor.printClassName(builder, clazz.asClassNamePattern());
- }
- }
-
- /**
- * Representation of conditional rules but without dependencies between condition and target.
- *
- * <pre>
- * -if class <class-condition> { <member-conditions> }
- * -keep class <class-target>
- * </pre>
- *
- * and with no dependencies / back-references.
- */
- static class PgConditionalClassRule extends PgConditionalRuleBase {
-
- public PgConditionalClassRule(
- KeepEdgeMetaInfo metaInfo,
- KeepOptions options,
- Holder classCondition,
- Holder classTarget,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions) {
- super(metaInfo, options, classCondition, classTarget, memberPatterns, memberConditions);
+ printClassHeader(builder, classTarget, this::printClassName);
+ if (getTargetMembers().isEmpty()) {
+ PgRule.printNonEmptyMembersPatternAsDefaultInitWorkaround(builder);
+ }
}
@Override
String getConsequenceKeepType() {
- return "-keep";
- }
-
- @Override
- List<String> getTargetMembers() {
- return Collections.emptyList();
- }
-
- @Override
- void printTargetMember(StringBuilder builder, String member) {
- throw new KeepEdgeException("Unreachable");
- }
- }
-
- /**
- * Representation of conditional rules but without dependencies between condition and target.
- *
- * <pre>
- * -if class <class-condition> { <member-conditions> }
- * -keep[classmembers] class <class-target> { <member-targets> }
- * </pre>
- *
- * and with no dependencies / back-references.
- */
- static class PgConditionalMemberRule extends PgConditionalRuleBase {
-
- private final List<String> memberTargets;
-
- public PgConditionalMemberRule(
- KeepEdgeMetaInfo metaInfo,
- KeepOptions options,
- Holder classCondition,
- Holder classTarget,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions,
- List<String> memberTargets) {
- super(metaInfo, options, classCondition, classTarget, memberPatterns, memberConditions);
- this.memberTargets = memberTargets;
- }
-
- @Override
- String getConsequenceKeepType() {
- return "-keepclassmembers";
+ return keepKind.getKeepRuleKind();
}
@Override
@@ -277,36 +268,75 @@
@Override
void printTargetMember(StringBuilder builder, String member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
- KeepRuleExtractor.printMemberClause(builder, memberPattern);
+ printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
+ }
+
+ private void printClassName(StringBuilder builder, KeepClassReference clazz) {
+ RulePrintingUtils.printClassName(
+ clazz.asClassNamePattern(), RulePrinter.withoutBackReferences(builder));
}
}
- abstract static class PgDependentRuleBase extends PgRule {
+ /**
+ * Representation of a conditional rule that is match/instance dependent.
+ *
+ * <pre>
+ * -if class <class-pattern> [{ <member-condition>* }]
+ * -keepX class <class-backref> [{ <member-target | member-backref>* }]
+ * </pre>
+ *
+ * or if the only condition is the class itself and targeting members, just:
+ *
+ * <pre>
+ * -keepclassmembers <class-pattern> { <member-target> }
+ * </pre>
+ */
+ static class PgDependentMembersRule extends PgRule {
- final KeepQualifiedClassNamePattern holderNamePattern;
- final KeepItemPattern holderPattern;
- final Map<String, KeepMemberPattern> memberPatterns;
- final List<String> memberConditions;
+ private final KeepQualifiedClassNamePattern holderNamePattern;
+ private final KeepItemPattern holderPattern;
+ private final Map<String, KeepMemberPattern> memberPatterns;
+ private final List<String> memberConditions;
+ private final List<String> memberTargets;
+ private final TargetKeepKind keepKind;
- public PgDependentRuleBase(
+ private int nextBackReferenceNumber = 1;
+ private String holderBackReferencePattern = null;
+ private Map<String, String> membersBackReferencePatterns = new HashMap<>();
+
+ public PgDependentMembersRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions) {
+ List<String> memberConditions,
+ List<String> memberTargets,
+ TargetKeepKind keepKind) {
super(metaInfo, options);
this.holderNamePattern = holder.namePattern;
this.holderPattern = holder.itemPattern;
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
+ this.memberTargets = memberTargets;
+ this.keepKind = keepKind;
}
- int nextBackReferenceNumber = 1;
- String holderBackReferencePattern;
- // TODO(b/248408342): Support back-ref to members too.
+ private int getNextBackReferenceNumber() {
+ return nextBackReferenceNumber++;
+ }
- private StringBuilder addBackRef(StringBuilder backReferenceBuilder) {
- return backReferenceBuilder.append('<').append(nextBackReferenceNumber++).append('>');
+ @Override
+ boolean hasCondition() {
+ // We can avoid an if-rule if the condition is simply the class and the target is just
+ // members.
+ boolean canUseDependentRule =
+ memberConditions.isEmpty() && keepKind == TargetKeepKind.JUST_MEMBERS;
+ return !canUseDependentRule;
+ }
+
+ @Override
+ String getConsequenceKeepType() {
+ return keepKind.getKeepRuleKind();
}
@Override
@@ -315,174 +345,64 @@
}
@Override
- void printConditionHolder(StringBuilder b) {
- KeepRuleExtractor.printClassHeader(
- b,
- holderPattern,
- (builder, classReference) -> {
- StringBuilder backReference = new StringBuilder();
- if (holderNamePattern.isAny()) {
- addBackRef(backReference);
- builder.append('*');
- } else {
- printPackagePrefix(builder, holderNamePattern.getPackagePattern(), backReference);
- printSimpleClassName(builder, holderNamePattern.getNamePattern(), backReference);
- }
- holderBackReferencePattern = backReference.toString();
- });
- }
-
- @Override
- void printConditionMember(StringBuilder builder, String member) {
- // TODO(b/248408342): Support back-ref to member instances too.
- KeepMemberPattern memberPattern = memberPatterns.get(member);
- KeepRuleExtractor.printMemberClause(builder, memberPattern);
- }
-
- @Override
- void printTargetHolder(StringBuilder builder) {
- KeepRuleExtractor.printClassHeader(
- builder,
- holderPattern,
- (b, reference) -> {
- assert reference.isBindingReference()
- || reference.asClassNamePattern().equals(holderNamePattern);
- b.append(holderBackReferencePattern);
- });
- }
-
- private StringBuilder printPackagePrefix(
- StringBuilder builder,
- KeepPackagePattern packagePattern,
- StringBuilder backReferenceBuilder) {
- if (packagePattern.isAny()) {
- addBackRef(backReferenceBuilder).append('.');
- return builder.append("**.");
- }
- if (packagePattern.isTop()) {
- return builder;
- }
- assert packagePattern.isExact();
- String exactPackage = packagePattern.getExactPackageAsString();
- backReferenceBuilder.append(exactPackage).append('.');
- return builder.append(exactPackage).append('.');
- }
-
- private StringBuilder printSimpleClassName(
- StringBuilder builder,
- KeepUnqualfiedClassNamePattern namePattern,
- StringBuilder backReferenceBuilder) {
- if (namePattern.isAny()) {
- addBackRef(backReferenceBuilder);
- return builder.append('*');
- }
- assert namePattern.isExact();
- String exactName = namePattern.asExact().getExactNameAsString();
- backReferenceBuilder.append(exactName);
- return builder.append(exactName);
- }
- }
-
- /**
- * Representation of a conditional class rule that is match/instance dependent.
- *
- * <pre>
- * -if class <class-pattern> { <member-condition>* }
- * -keep class <class-backref>
- * </pre>
- */
- static class PgDependentClassRule extends PgDependentRuleBase {
-
- public PgDependentClassRule(
- KeepEdgeMetaInfo metaInfo,
- Holder holder,
- KeepOptions options,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions) {
- super(metaInfo, holder, options, memberPatterns, memberConditions);
- }
-
- @Override
- String getConsequenceKeepType() {
- return "-keep";
- }
-
- @Override
- boolean hasCondition() {
- return true;
- }
-
- @Override
- List<String> getTargetMembers() {
- return Collections.emptyList();
- }
-
- @Override
- void printTargetMember(StringBuilder builder, String member) {
- throw new KeepEdgeException("Unreachable");
- }
- }
-
- /**
- * Representation of a conditional member rule that is match/instance dependent.
- *
- * <pre>
- * -if class <class-pattern> { <member-condition>* }
- * -keepclassmembers class <class-backref> { <member-target | member-backref>* }
- * </pre>
- *
- * or if the only condition is the class itself, just:
- *
- * <pre>
- * -keepclassmembers <class-pattern> { <member-target> }
- * </pre>
- */
- static class PgDependentMembersRule extends PgDependentRuleBase {
-
- final List<String> memberTargets;
-
- public PgDependentMembersRule(
- KeepEdgeMetaInfo metaInfo,
- Holder holder,
- KeepOptions options,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions,
- List<String> memberTargets) {
- super(metaInfo, holder, options, memberPatterns, memberConditions);
- assert !memberTargets.isEmpty();
- this.memberTargets = memberTargets;
- }
-
- @Override
- boolean hasCondition() {
- return !memberConditions.isEmpty();
- }
-
- @Override
- String getConsequenceKeepType() {
- return "-keepclassmembers";
- }
-
- @Override
List<String> getTargetMembers() {
return memberTargets;
}
@Override
+ void printConditionHolder(StringBuilder b) {
+ printClassHeader(
+ b,
+ holderPattern,
+ (builder, classReference) -> {
+ BackReferencePrinter printer =
+ RulePrinter.withBackReferences(b, this::getNextBackReferenceNumber);
+ RulePrintingUtils.printClassName(holderNamePattern, printer);
+ holderBackReferencePattern = printer.getBackReference();
+ });
+ }
+
+ @Override
+ void printConditionMember(StringBuilder builder, String member) {
+ KeepMemberPattern memberPattern = memberPatterns.get(member);
+ BackReferencePrinter printer =
+ RulePrinter.withBackReferences(builder, this::getNextBackReferenceNumber);
+ printMemberClause(memberPattern, printer);
+ membersBackReferencePatterns.put(member, printer.getBackReference());
+ }
+
+ @Override
void printTargetHolder(StringBuilder builder) {
- if (hasCondition()) {
- super.printTargetHolder(builder);
- } else {
- KeepRuleExtractor.printClassHeader(
- builder, holderPattern, classReferencePrinter(holderNamePattern));
+ printClassHeader(
+ builder,
+ holderPattern,
+ (b, reference) -> {
+ assert reference.isBindingReference()
+ || reference.asClassNamePattern().equals(holderNamePattern);
+ if (hasCondition()) {
+ b.append(holderBackReferencePattern);
+ } else {
+ assert holderBackReferencePattern == null;
+ RulePrintingUtils.printClassName(
+ holderNamePattern, RulePrinter.withoutBackReferences(builder));
+ }
+ });
+ if (getTargetMembers().isEmpty()) {
+ PgRule.printNonEmptyMembersPatternAsDefaultInitWorkaround(builder);
}
}
@Override
void printTargetMember(StringBuilder builder, String member) {
- // TODO(b/248408342): Support back-ref to member instances too.
+ if (hasCondition()) {
+ String backref = membersBackReferencePatterns.get(member);
+ if (backref != null) {
+ builder.append(backref);
+ return;
+ }
+ }
KeepMemberPattern memberPattern = memberPatterns.get(member);
- KeepRuleExtractor.printMemberClause(builder, memberPattern);
+ printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
new file mode 100644
index 0000000..94694a2
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
@@ -0,0 +1,99 @@
+// Copyright (c) 2023, 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.keeprules;
+
+import java.util.function.Supplier;
+
+public class RulePrinter {
+
+ public static RulePrinter withoutBackReferences(StringBuilder builder) {
+ return new RulePrinter(builder);
+ }
+
+ public static BackReferencePrinter withBackReferences(
+ StringBuilder builder, Supplier<Integer> nextReferenceGenerator) {
+ return new BackReferencePrinter(builder, nextReferenceGenerator);
+ }
+
+ private final StringBuilder builder;
+
+ private RulePrinter(StringBuilder builder) {
+ this.builder = builder;
+ }
+
+ public RulePrinter append(String str) {
+ assert !str.contains("*");
+ assert !str.contains("(...)");
+ return appendWithoutBackReferenceAssert(str);
+ }
+
+ public RulePrinter appendWithoutBackReferenceAssert(String str) {
+ builder.append(str);
+ return this;
+ }
+
+ public RulePrinter appendStar() {
+ return appendWithoutBackReferenceAssert("*");
+ }
+
+ public RulePrinter appendDoubleStar() {
+ return appendWithoutBackReferenceAssert("**");
+ }
+
+ public RulePrinter appendTripleStar() {
+ return appendWithoutBackReferenceAssert("***");
+ }
+
+ public RulePrinter appendAnyParameters() {
+ return appendWithoutBackReferenceAssert("(...)");
+ }
+
+ /** Printer with support for collecting the back-reference printing. */
+ public static class BackReferencePrinter extends RulePrinter {
+
+ final Supplier<Integer> nextNumberGenerator;
+ final StringBuilder backref = new StringBuilder();
+
+ private BackReferencePrinter(StringBuilder builder, Supplier<Integer> nextNumberGenerator) {
+ super(builder);
+ this.nextNumberGenerator = nextNumberGenerator;
+ }
+
+ public String getBackReference() {
+ return backref.toString();
+ }
+
+ private RulePrinter addBackRef(String wildcard) {
+ backref.append('<').append(nextNumberGenerator.get()).append('>');
+ return super.appendWithoutBackReferenceAssert(wildcard);
+ }
+
+ @Override
+ public RulePrinter appendWithoutBackReferenceAssert(String str) {
+ backref.append(str);
+ return super.appendWithoutBackReferenceAssert(str);
+ }
+
+ @Override
+ public RulePrinter appendStar() {
+ return addBackRef("*");
+ }
+
+ @Override
+ public RulePrinter appendDoubleStar() {
+ return addBackRef("**");
+ }
+
+ @Override
+ public RulePrinter appendTripleStar() {
+ return addBackRef("***");
+ }
+
+ @Override
+ public RulePrinter appendAnyParameters() {
+ // TODO(b/265892343): R8 does not yet support back reference to `...`.
+ return appendWithoutBackReferenceAssert("(...)");
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
new file mode 100644
index 0000000..6f08712
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -0,0 +1,307 @@
+// Copyright (c) 2023, 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.keeprules;
+
+import com.android.tools.r8.keepanno.ast.KeepClassReference;
+import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
+import com.android.tools.r8.keepanno.ast.KeepExtendsPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepOptions;
+import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
+import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.keepanno.utils.Unimplemented;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+public abstract class RulePrintingUtils {
+
+ public static final String IF = "-if";
+ public static final String KEEP = "-keep";
+ public static final String KEEP_CLASS_MEMBERS = "-keepclassmembers";
+ public static final String KEEP_CLASSES_WITH_MEMBERS = "-keepclasseswithmembers";
+
+ public static void printHeader(StringBuilder builder, KeepEdgeMetaInfo metaInfo) {
+ if (metaInfo.hasContext()) {
+ builder.append("# context: ").append(metaInfo.getContextDescriptorString()).append('\n');
+ }
+ if (metaInfo.hasDescription()) {
+ String escapedDescription = escapeLineBreaks(metaInfo.getDescriptionString());
+ builder.append("# description: ").append(escapedDescription).append('\n');
+ }
+ }
+
+ public static String escapeChar(char c) {
+ if (c == '\n') {
+ return "\\n";
+ }
+ if (c == '\r') {
+ return "\\r";
+ }
+ return null;
+ }
+
+ public static String escapeLineBreaks(String string) {
+ char[] charArray = string.toCharArray();
+ for (int i = 0; i < charArray.length; i++) {
+ // We don't expect escape chars, so wait with constructing a new string until found.
+ if (escapeChar(charArray[i]) != null) {
+ StringBuilder builder = new StringBuilder(string.substring(0, i));
+ for (int j = i; j < charArray.length; j++) {
+ char c = charArray[j];
+ String escaped = escapeChar(c);
+ if (escaped != null) {
+ builder.append(escaped);
+ } else {
+ builder.append(c);
+ }
+ }
+ return builder.toString();
+ }
+ }
+ return string;
+ }
+
+ public static void printKeepOptions(StringBuilder builder, KeepOptions options) {
+ for (KeepOption option : KeepOption.values()) {
+ if (options.isAllowed(option)) {
+ builder.append(",allow").append(getOptionString(option));
+ }
+ }
+ }
+
+ public static StringBuilder printClassHeader(
+ StringBuilder builder,
+ KeepItemPattern classPattern,
+ BiConsumer<StringBuilder, KeepClassReference> printClassReference) {
+ builder.append("class ");
+ printClassReference.accept(builder, classPattern.getClassReference());
+ KeepExtendsPattern extendsPattern = classPattern.getExtendsPattern();
+ if (!extendsPattern.isAny()) {
+ builder.append(" extends ");
+ printClassName(
+ extendsPattern.asClassNamePattern(), RulePrinter.withoutBackReferences(builder));
+ }
+ return builder;
+ }
+
+ public static RulePrinter printMemberClause(KeepMemberPattern member, RulePrinter printer) {
+ if (member.isAllMembers()) {
+ // Note: the rule language does not allow backref to a full member. A rule matching all
+ // members via a binding must be split in two up front: one for methods and one for fields.
+ return printer.appendWithoutBackReferenceAssert("*").append(";");
+ }
+ if (member.isMethod()) {
+ return printMethod(member.asMethod(), printer);
+ }
+ if (member.isField()) {
+ return printField(member.asField(), printer);
+ }
+ throw new Unimplemented();
+ }
+
+ private static RulePrinter printField(KeepFieldPattern fieldPattern, RulePrinter builder) {
+ printFieldAccess(builder, " ", fieldPattern.getAccessPattern());
+ printType(builder, fieldPattern.getTypePattern().asType());
+ builder.append(" ");
+ printFieldName(builder, fieldPattern.getNamePattern());
+ return builder.append(";");
+ }
+
+ private static RulePrinter printMethod(KeepMethodPattern methodPattern, RulePrinter builder) {
+ printMethodAccess(builder, " ", methodPattern.getAccessPattern());
+ printReturnType(builder, methodPattern.getReturnTypePattern());
+ builder.append(" ");
+ printMethodName(builder, methodPattern.getNamePattern());
+ printParameters(builder, methodPattern.getParametersPattern());
+ return builder.append(";");
+ }
+
+ private static RulePrinter printParameters(
+ RulePrinter builder, KeepMethodParametersPattern parametersPattern) {
+ if (parametersPattern.isAny()) {
+ return builder.appendAnyParameters();
+ }
+ builder.append("(");
+ List<KeepTypePattern> patterns = parametersPattern.asList();
+ for (int i = 0; i < patterns.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ printType(builder, patterns.get(i));
+ }
+ return builder.append(")");
+ }
+
+ private static RulePrinter printFieldName(RulePrinter builder, KeepFieldNamePattern namePattern) {
+ return namePattern.isAny()
+ ? builder.appendStar()
+ : builder.append(namePattern.asExact().getName());
+ }
+
+ private static RulePrinter printMethodName(
+ RulePrinter builder, KeepMethodNamePattern namePattern) {
+ return namePattern.isAny()
+ ? builder.appendStar()
+ : builder.append(namePattern.asExact().getName());
+ }
+
+ private static RulePrinter printReturnType(
+ RulePrinter builder, KeepMethodReturnTypePattern returnTypePattern) {
+ if (returnTypePattern.isVoid()) {
+ return builder.append("void");
+ }
+ return printType(builder, returnTypePattern.asType());
+ }
+
+ private static RulePrinter printType(RulePrinter builder, KeepTypePattern typePattern) {
+ if (typePattern.isAny()) {
+ return builder.appendTripleStar();
+ }
+ return builder.append(descriptorToJavaType(typePattern.getDescriptor()));
+ }
+
+ private static RulePrinter printMethodAccess(
+ RulePrinter builder, String indent, KeepMethodAccessPattern accessPattern) {
+ if (accessPattern.isAny()) {
+ // No text will match any access pattern.
+ // Don't print the indent in this case.
+ return builder;
+ }
+ throw new Unimplemented();
+ }
+
+ private static RulePrinter printFieldAccess(
+ RulePrinter builder, String indent, KeepFieldAccessPattern accessPattern) {
+ if (accessPattern.isAny()) {
+ // No text will match any access pattern.
+ // Don't print the indent in this case.
+ return builder;
+ }
+ throw new Unimplemented();
+ }
+
+ public static RulePrinter printClassName(
+ KeepQualifiedClassNamePattern classNamePattern, RulePrinter printer) {
+ if (classNamePattern.isAny()) {
+ return printer.appendStar();
+ }
+ printPackagePrefix(classNamePattern.getPackagePattern(), printer);
+ return printSimpleClassName(classNamePattern.getNamePattern(), printer);
+ }
+
+ private static RulePrinter printPackagePrefix(
+ KeepPackagePattern packagePattern, RulePrinter builder) {
+ if (packagePattern.isAny()) {
+ return builder.appendDoubleStar().append(".");
+ }
+ if (packagePattern.isTop()) {
+ return builder;
+ }
+ assert packagePattern.isExact();
+ return builder.append(packagePattern.getExactPackageAsString()).append(".");
+ }
+
+ private static RulePrinter printSimpleClassName(
+ KeepUnqualfiedClassNamePattern namePattern, RulePrinter builder) {
+ if (namePattern.isAny()) {
+ return builder.appendStar();
+ }
+ assert namePattern.isExact();
+ return builder.append(namePattern.asExact().getExactNameAsString());
+ }
+
+ private static String getOptionString(KeepOption option) {
+ switch (option) {
+ case SHRINKING:
+ return "shrinking";
+ case OPTIMIZING:
+ return "optimization";
+ case OBFUSCATING:
+ return "obfuscation";
+ case ACCESS_MODIFICATION:
+ return "accessmodification";
+ case ANNOTATION_REMOVAL:
+ return "annotationremoval";
+ default:
+ throw new Unimplemented();
+ }
+ }
+
+ private static String getTypePatternString(KeepTypePattern typePattern) {
+ if (typePattern.isAny()) {
+ return "***";
+ }
+ return descriptorToJavaType(typePattern.getDescriptor());
+ }
+
+ private static String descriptorToJavaType(String descriptor) {
+ if (descriptor.isEmpty()) {
+ throw new KeepEdgeException("Invalid empty type descriptor");
+ }
+ if (descriptor.length() == 1) {
+ return primitiveDescriptorToJavaType(descriptor.charAt(0));
+ }
+ if (descriptor.charAt(0) == '[') {
+ return arrayDescriptorToJavaType(descriptor);
+ }
+ return classDescriptorToJavaType(descriptor);
+ }
+
+ private static String primitiveDescriptorToJavaType(char descriptor) {
+ switch (descriptor) {
+ case 'Z':
+ return "boolean";
+ case 'B':
+ return "byte";
+ case 'S':
+ return "short";
+ case 'I':
+ return "int";
+ case 'J':
+ return "long";
+ case 'F':
+ return "float";
+ case 'D':
+ return "double";
+ default:
+ throw new KeepEdgeException("Invalid primitive descriptor: " + descriptor);
+ }
+ }
+
+ private static String classDescriptorToJavaType(String descriptor) {
+ int last = descriptor.length() - 1;
+ if (descriptor.charAt(0) != 'L' || descriptor.charAt(last) != ';') {
+ throw new KeepEdgeException("Invalid class descriptor: " + descriptor);
+ }
+ return descriptor.substring(1, last).replace('/', '.');
+ }
+
+ private static String arrayDescriptorToJavaType(String descriptor) {
+ for (int i = 0; i < descriptor.length(); i++) {
+ char c = descriptor.charAt(i);
+ if (c != '[') {
+ StringBuilder builder = new StringBuilder();
+ builder.append(descriptorToJavaType(descriptor.substring(i)));
+ for (int j = 0; j < i; j++) {
+ builder.append("[]");
+ }
+ return builder.toString();
+ }
+ }
+ throw new KeepEdgeException("Invalid array descriptor: " + descriptor);
+ }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 1132b9a..51350bc 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -77,16 +77,6 @@
{
"api_level_below_or_equal": 32,
"api_level_greater_or_equal": 24,
- "emulate_interface": {
- "java.util.Collection": "j$.util.Collection"
- },
- "dont_rewrite": [
- "boolean java.util.Collection#removeIf(java.util.function.Predicate)",
- "java.util.Spliterator java.util.Collection#spliterator()",
- "java.util.stream.Stream java.util.Collection#parallelStream()",
- "java.util.stream.Stream java.util.Collection#stream()",
- "void java.util.Collection#forEach(java.util.function.Consumer)"
- ],
"rewrite_prefix": {
"java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
},
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index cd20a37..6cdf118 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -93,16 +93,6 @@
{
"api_level_below_or_equal": 32,
"api_level_greater_or_equal": 24,
- "emulate_interface": {
- "java.util.Collection": "j$.util.Collection"
- },
- "dont_rewrite": [
- "boolean java.util.Collection#removeIf(java.util.function.Predicate)",
- "java.util.Spliterator java.util.Collection#spliterator()",
- "java.util.stream.Stream java.util.Collection#parallelStream()",
- "java.util.stream.Stream java.util.Collection#stream()",
- "void java.util.Collection#forEach(java.util.function.Consumer)"
- ],
"rewrite_prefix": {
"java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
},
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 35d22bb..d7cbd04 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.utils.InternalOptions.DesugarState;
import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.InternalOptions.MappingComposeOptions;
import com.android.tools.r8.utils.ProgramClassCollection;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -636,7 +637,8 @@
!internal.debug && proguardMapConsumer != null
? LineNumberOptimization.ON
: LineNumberOptimization.OFF;
-
+ MappingComposeOptions mappingComposeOptions = internal.mappingComposeOptions();
+ mappingComposeOptions.enableExperimentalMappingComposition = true;
// Assert and fixup defaults.
assert !internal.isShrinking();
assert !internal.isMinifying();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 88f233f..70dc6f3 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -102,7 +102,6 @@
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
-import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SelfRetraceTest;
@@ -409,24 +408,16 @@
}
TreePruner pruner = new TreePruner(appViewWithLiveness);
- DirectMappedDexApplication prunedApp = pruner.run(executorService);
+ PrunedItems prunedItems = pruner.run(executorService, timing);
// Recompute the subtyping information.
- Set<DexType> removedClasses = pruner.getRemovedClasses();
- appView.pruneItems(
- PrunedItems.builder()
- .setPrunedApp(prunedApp)
- .addRemovedClasses(removedClasses)
- .addAdditionalPinnedItems(pruner.getMethodsToKeepForConfigurationDebugging())
- .build(),
- executorService);
+ appView.pruneItems(prunedItems, executorService);
new AbstractMethodRemover(
appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
.run();
AnnotationRemover annotationRemover =
- annotationRemoverBuilder
- .build(appViewWithLiveness, removedClasses);
+ annotationRemoverBuilder.build(appViewWithLiveness, prunedItems.getRemovedClasses());
annotationRemover.ensureValid().run(executorService);
new GenericSignatureRewriter(appView, genericContextBuilder)
.run(appView.appInfo().classes(), executorService);
@@ -608,21 +599,14 @@
GenericSignatureContextBuilder.create(appView);
TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
- DirectMappedDexApplication application = pruner.run(executorService);
- Set<DexType> removedClasses = pruner.getRemovedClasses();
+ PrunedItems prunedItems = pruner.run(executorService, timing, prunedTypes);
if (options.usageInformationConsumer != null) {
ExceptionUtils.withFinishedResourceHandler(
options.reporter, options.usageInformationConsumer);
}
- appView.pruneItems(
- PrunedItems.builder()
- .setPrunedApp(application)
- .addRemovedClasses(CollectionUtils.mergeSets(prunedTypes, removedClasses))
- .addAdditionalPinnedItems(pruner.getMethodsToKeepForConfigurationDebugging())
- .build(),
- executorService);
+ appView.pruneItems(prunedItems, executorService);
new BridgeHoisting(appViewWithLiveness).run();
@@ -646,7 +630,7 @@
// Remove annotations that refer to types that no longer exist.
AnnotationRemover.builder(Mode.FINAL_TREE_SHAKING)
- .build(appView.withLiveness(), removedClasses)
+ .build(appView.withLiveness(), prunedItems.getRemovedClasses())
.run(executorService);
new GenericSignatureRewriter(appView, genericContextBuilder)
.run(appView.appInfo().classes(), executorService);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
index 826b8dc..77f653a 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriterExperimental.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
import com.android.tools.r8.dex.FileWriter.DexContainerSection;
+import com.android.tools.r8.dex.FileWriter.MapItem;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -176,31 +177,31 @@
DexOutputBuffer dexOutputBuffer, List<DexContainerSection> sections) {
// The last section has the shared string_ids table. Now it is written the final size and
// offset is known and the remaining sections can be updated to point to the shared table.
- // This updates both the size and offset in the header and in the map.
DexContainerSection lastSection = ListUtils.last(sections);
int stringIdsSize = lastSection.getFileWriter().getMixedSectionOffsets().getStringData().size();
int stringIdsOffset = lastSection.getLayout().stringIdsOffset;
for (DexContainerSection section : sections) {
if (section != lastSection) {
+ // Update the string_ids size and offset in the header.
dexOutputBuffer.moveTo(section.getLayout().headerOffset + Constants.STRING_IDS_SIZE_OFFSET);
dexOutputBuffer.putInt(stringIdsSize);
dexOutputBuffer.putInt(stringIdsOffset);
+ // Write the map. The map is sorted by offset, so write all entries after setting
+ // string_ids and sorting.
dexOutputBuffer.moveTo(section.getLayout().getMapOffset());
- // Skip size.
- dexOutputBuffer.getInt();
- while (dexOutputBuffer.position() < section.getLayout().getEndOfFile()) {
- int sectionType = dexOutputBuffer.getShort();
- dexOutputBuffer.getShort(); // Skip unused.
- if (sectionType == Constants.TYPE_STRING_ID_ITEM) {
- dexOutputBuffer.putInt(stringIdsSize);
- dexOutputBuffer.putInt(stringIdsOffset);
- break;
- } else {
- // Skip size and offset for this type.
- dexOutputBuffer.getInt();
- dexOutputBuffer.getInt();
- }
+ List<MapItem> mapItems =
+ section
+ .getLayout()
+ .generateMapInfo(section.getFileWriter(), stringIdsSize, stringIdsOffset);
+ int originalSize = dexOutputBuffer.getInt();
+ int size = 0;
+ for (MapItem mapItem : mapItems) {
+ size += mapItem.write(dexOutputBuffer);
}
+ assert originalSize == size;
+ // Calculate signature and checksum after the map is written.
+ section.getFileWriter().writeSignature(section.getLayout(), dexOutputBuffer);
+ section.getFileWriter().writeChecksum(section.getLayout(), dexOutputBuffer);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 9ddc2d2..fc9c037 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -66,6 +66,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
@@ -292,8 +293,7 @@
this::writeAnnotationDirectory,
4);
- // Add the map at the end
- layout.setMapOffset(dest.align(4));
+ // Add the map at the end.
writeMap(layout);
layout.setEndOfFile(dest.position());
@@ -315,8 +315,10 @@
// Fill in the header information.
writeHeader(layout);
- writeSignature(layout);
- writeChecksum(layout);
+ if (includeStringData) {
+ writeSignature(layout);
+ writeChecksum(layout);
+ }
// Wrap backing buffer with actual length.
return new DexContainerSection(this, dest, layout);
@@ -777,70 +779,15 @@
}
}
- private int writeMapItem(int type, int offset, int length) {
- if (length == 0) {
- return 0;
- }
- if (Log.ENABLED) {
- Log.debug(getClass(), "Map entry 0x%04x @ 0x%08x # %08d.", type, offset, length);
- }
- dest.putShort((short) type);
- dest.putShort((short) 0);
- dest.putInt(length);
- dest.putInt(offset);
- return 1;
- }
-
- private void writeMap(Layout layout) {
+ public void writeMap(Layout layout) {
int startOfMap = dest.align(4);
+ layout.setMapOffset(startOfMap);
dest.forward(4); // Leave space for size;
+ List<MapItem> mapItems = layout.generateMapInfo(this);
int size = 0;
- size += writeMapItem(Constants.TYPE_HEADER_ITEM, 0, 1);
- size += writeMapItem(Constants.TYPE_STRING_ID_ITEM, layout.stringIdsOffset,
- mapping.getStrings().size());
- size += writeMapItem(Constants.TYPE_TYPE_ID_ITEM, layout.typeIdsOffset,
- mapping.getTypes().size());
- size += writeMapItem(Constants.TYPE_PROTO_ID_ITEM, layout.protoIdsOffset,
- mapping.getProtos().size());
- size += writeMapItem(Constants.TYPE_FIELD_ID_ITEM, layout.fieldIdsOffset,
- mapping.getFields().size());
- size += writeMapItem(Constants.TYPE_METHOD_ID_ITEM, layout.methodIdsOffset,
- mapping.getMethods().size());
- size += writeMapItem(Constants.TYPE_CLASS_DEF_ITEM, layout.classDefsOffset,
- mapping.getClasses().length);
- size += writeMapItem(Constants.TYPE_CALL_SITE_ID_ITEM, layout.callSiteIdsOffset,
- mapping.getCallSites().size());
- size += writeMapItem(Constants.TYPE_METHOD_HANDLE_ITEM, layout.methodHandleIdsOffset,
- mapping.getMethodHandles().size());
- size += writeMapItem(Constants.TYPE_CODE_ITEM, layout.getCodesOffset(),
- mixedSectionOffsets.getCodes().size());
- size += writeMapItem(Constants.TYPE_DEBUG_INFO_ITEM, layout.getDebugInfosOffset(),
- mixedSectionOffsets.getDebugInfos().size());
- size += writeMapItem(Constants.TYPE_TYPE_LIST, layout.getTypeListsOffset(),
- mixedSectionOffsets.getTypeLists().size());
- size +=
- writeMapItem(
- Constants.TYPE_STRING_DATA_ITEM,
- layout.getStringDataOffsets(),
- layout.getStringDataOffsets() == 0 ? 0 : mixedSectionOffsets.getStringData().size());
- size += writeMapItem(Constants.TYPE_ANNOTATION_ITEM, layout.getAnnotationsOffset(),
- mixedSectionOffsets.getAnnotations().size());
- size += writeMapItem(Constants.TYPE_CLASS_DATA_ITEM, layout.getClassDataOffset(),
- mixedSectionOffsets.getClassesWithData().size());
- size +=
- writeMapItem(
- Constants.TYPE_ENCODED_ARRAY_ITEM,
- layout.getEncodedArraysOffset(),
- mixedSectionOffsets.getEncodedArrays().size());
- size += writeMapItem(Constants.TYPE_ANNOTATION_SET_ITEM, layout.getAnnotationSetsOffset(),
- mixedSectionOffsets.getAnnotationSets().size());
- size += writeMapItem(Constants.TYPE_ANNOTATION_SET_REF_LIST,
- layout.getAnnotationSetRefListsOffset(),
- mixedSectionOffsets.getAnnotationSetRefLists().size());
- size += writeMapItem(Constants.TYPE_ANNOTATIONS_DIRECTORY_ITEM,
- layout.getAnnotationDirectoriesOffset(),
- mixedSectionOffsets.getAnnotationDirectories().size());
- size += writeMapItem(Constants.TYPE_MAP_LIST, layout.getMapOffset(), 1);
+ for (MapItem mapItem : mapItems) {
+ size += includeStringData ? mapItem.write(dest) : mapItem.size();
+ }
dest.moveTo(startOfMap);
dest.putInt(size);
dest.forward(size * Constants.TYPE_MAP_LIST_ITEM_SIZE);
@@ -892,26 +839,34 @@
}
private void writeSignature(Layout layout) {
+ writeSignature(layout, dest);
+ }
+
+ public void writeSignature(Layout layout, DexOutputBuffer dexOutputBuffer) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(
- dest.asArray(),
+ dexOutputBuffer.asArray(),
layout.headerOffset + Constants.FILE_SIZE_OFFSET,
layout.getEndOfFile() - layout.headerOffset - Constants.FILE_SIZE_OFFSET);
- md.digest(dest.asArray(), layout.headerOffset + Constants.SIGNATURE_OFFSET, 20);
+ md.digest(dexOutputBuffer.asArray(), layout.headerOffset + Constants.SIGNATURE_OFFSET, 20);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void writeChecksum(Layout layout) {
+ writeChecksum(layout, dest);
+ }
+
+ public void writeChecksum(Layout layout, DexOutputBuffer dexOutputBuffer) {
Adler32 adler = new Adler32();
adler.update(
- dest.asArray(),
+ dexOutputBuffer.asArray(),
layout.headerOffset + Constants.SIGNATURE_OFFSET,
layout.getEndOfFile() - layout.headerOffset - Constants.SIGNATURE_OFFSET);
- dest.moveTo(layout.headerOffset + Constants.CHECKSUM_OFFSET);
- dest.putInt((int) adler.getValue());
+ dexOutputBuffer.moveTo(layout.headerOffset + Constants.CHECKSUM_OFFSET);
+ dexOutputBuffer.putInt((int) adler.getValue());
}
private static int alignSize(int bytes, int value) {
@@ -919,6 +874,48 @@
return (value + mask) & ~mask;
}
+ public static class MapItem {
+ final int type;
+ final int offset;
+ final int length;
+
+ public MapItem(int type, int offset, int size) {
+ this.type = type;
+ this.offset = offset;
+ this.length = size;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public int write(DexOutputBuffer dest) {
+ if (length == 0) {
+ return 0;
+ }
+ if (Log.ENABLED) {
+ Log.debug(getClass(), "Map entry 0x%04x @ 0x%08x # %08d.", type, offset, length);
+ }
+ dest.putShort((short) type);
+ dest.putShort((short) 0);
+ dest.putInt(length);
+ dest.putInt(offset);
+ return 1;
+ }
+
+ public int size() {
+ return length == 0 ? 0 : 1;
+ }
+ }
+
public static class Layout {
private static final int NOT_SET = -1;
@@ -1124,6 +1121,104 @@
this.mapOffset = mapOffset;
}
+ public List<MapItem> generateMapInfo(FileWriter fileWriter) {
+ return generateMapInfo(
+ fileWriter, fileWriter.mixedSectionOffsets.getStringData().size(), stringIdsOffset);
+ }
+
+ public List<MapItem> generateMapInfo(
+ FileWriter fileWriter, int stringIdsSize, int stringIdsOffset) {
+ List<MapItem> mapItems = new ArrayList<>();
+ mapItems.add(new MapItem(Constants.TYPE_HEADER_ITEM, 0, 1));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_STRING_ID_ITEM,
+ stringIdsOffset,
+ fileWriter.mapping.getStrings().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_TYPE_ID_ITEM, typeIdsOffset, fileWriter.mapping.getTypes().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_PROTO_ID_ITEM, protoIdsOffset, fileWriter.mapping.getProtos().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_FIELD_ID_ITEM, fieldIdsOffset, fileWriter.mapping.getFields().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_METHOD_ID_ITEM,
+ methodIdsOffset,
+ fileWriter.mapping.getMethods().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_CLASS_DEF_ITEM,
+ classDefsOffset,
+ fileWriter.mapping.getClasses().length));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_CALL_SITE_ID_ITEM,
+ callSiteIdsOffset,
+ fileWriter.mapping.getCallSites().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_METHOD_HANDLE_ITEM,
+ methodHandleIdsOffset,
+ fileWriter.mapping.getMethodHandles().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_CODE_ITEM,
+ getCodesOffset(),
+ fileWriter.mixedSectionOffsets.getCodes().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_DEBUG_INFO_ITEM,
+ getDebugInfosOffset(),
+ fileWriter.mixedSectionOffsets.getDebugInfos().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_TYPE_LIST,
+ getTypeListsOffset(),
+ fileWriter.mixedSectionOffsets.getTypeLists().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_STRING_DATA_ITEM,
+ getStringDataOffsets(),
+ getStringDataOffsets() == 0 ? 0 : stringIdsSize));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_ANNOTATION_ITEM,
+ getAnnotationsOffset(),
+ fileWriter.mixedSectionOffsets.getAnnotations().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_CLASS_DATA_ITEM,
+ getClassDataOffset(),
+ fileWriter.mixedSectionOffsets.getClassesWithData().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_ENCODED_ARRAY_ITEM,
+ getEncodedArraysOffset(),
+ fileWriter.mixedSectionOffsets.getEncodedArrays().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_ANNOTATION_SET_ITEM,
+ getAnnotationSetsOffset(),
+ fileWriter.mixedSectionOffsets.getAnnotationSets().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_ANNOTATION_SET_REF_LIST,
+ getAnnotationSetRefListsOffset(),
+ fileWriter.mixedSectionOffsets.getAnnotationSetRefLists().size()));
+ mapItems.add(
+ new MapItem(
+ Constants.TYPE_ANNOTATIONS_DIRECTORY_ITEM,
+ getAnnotationDirectoriesOffset(),
+ fileWriter.mixedSectionOffsets.getAnnotationDirectories().size()));
+ mapItems.add(new MapItem(Constants.TYPE_MAP_LIST, getMapOffset(), 1));
+ mapItems.sort(Comparator.comparingInt(MapItem::getOffset));
+ return mapItems;
+ }
+
public int getEndOfFile() {
return endOfFile;
}
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 68c1dde..f49fcbd 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -161,11 +161,21 @@
return this;
}
+ public Builder addRemovedFields(Collection<DexField> removedFields) {
+ this.removedFields.addAll(removedFields);
+ return this;
+ }
+
public Builder addRemovedMethod(DexMethod removedMethod) {
removedMethods.add(removedMethod);
return this;
}
+ public Builder addRemovedMethods(Collection<DexMethod> removedMethods) {
+ this.removedMethods.addAll(removedMethods);
+ return this;
+ }
+
public Builder setRemovedClasses(Set<DexType> removedClasses) {
this.removedClasses = removedClasses;
return this;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index 752964b..18ab413 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -459,6 +459,23 @@
return this;
}
+ StringBuilderWithIndent appendLineStart(String lineStart) {
+ builder.append(indent);
+ builder.append(lineStart);
+ return this;
+ }
+
+ StringBuilderWithIndent append(String string) {
+ builder.append(string);
+ return this;
+ }
+
+ StringBuilderWithIndent appendLineEnd(String lineEnd) {
+ builder.append(lineEnd);
+ builder.append(NL);
+ return this;
+ }
+
StringBuilderWithIndent appendLine(String line) {
builder.append(indent);
builder.append(line);
@@ -668,8 +685,21 @@
indent(indent);
}
- HTMLBuilder appendTdCode(String s) {
- appendLine("<td><code>" + s + "</code></td>");
+ HTMLBuilder appendTdPackage(String s) {
+ appendLineStart("<td><code><em>" + s + "</em></code><br>");
+ if (s.startsWith("java.time")) {
+ append("<a href=\"#java-time-customizations\">See customizations</a><br");
+ } else if (s.startsWith("java.nio")) {
+ append("<a href=\"#java-nio-customizations\">See customizations</a><br");
+ }
+ return this;
+ }
+
+ HTMLBuilder appendTdClassName(String s) {
+ appendLineEnd(
+ "<code><br><br><div style=\"font-size:small;font-weight:bold;\"> "
+ + s
+ + "</div></code><br><br></td>");
return this;
}
@@ -679,7 +709,7 @@
}
HTMLBuilder appendLiCode(String s) {
- appendLine("<li><code>" + s + "</code></li>");
+ appendLine("<li class=\"java8_table\"><code>" + s + "</code></li>");
return this;
}
@@ -715,10 +745,14 @@
HTMLBuilder builder = new HTMLBuilder();
builder.start("tr");
if (packageName.length() > 0) {
- builder.appendTdCode(packageName);
+ builder.appendTdPackage(packageName);
}
- builder.appendTdCode(typeInPackage(className));
- builder.start("td").start("ul");
+ builder.appendTdClassName(typeInPackage(className));
+ builder
+ .start("td")
+ .start(
+ "ul style=\"list-style-position:inside; list-style-type: none !important;"
+ + " margin-left:0px;padding-left:0px !important;\"");
if (!fields.isEmpty()) {
assert newClass; // Currently no fields are added to existing classes.
for (DexEncodedField field : fields) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
index 428bdd0..310856f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
@@ -28,7 +28,7 @@
/** This class defines the desugaring of a single invoke-special instruction. */
public class InvokeSpecialToSelfDesugaring implements CfInstructionDesugaring {
- private static final String INVOKE_SPECIAL_BRIDGE_PREFIX = "$invoke$special$";
+ public static final String INVOKE_SPECIAL_BRIDGE_PREFIX = "$invoke$special$";
private final DexItemFactory dexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 91da4d9..42f22a6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar.itf;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode.ALL;
import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode.EMULATED_INTERFACE_ONLY;
import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode.NONE;
@@ -388,8 +387,6 @@
this.dexItemFactory = appView.dexItemFactory();
this.helper = new InterfaceDesugaringSyntheticHelper(appView);
assert desugaringMode != NONE;
- assert desugaringMode == ALL
- || !appView.options().machineDesugaredLibrarySpecification.isEmpty();
needsLibraryInfo = !appView.options().machineDesugaredLibrarySpecification.isEmpty();
this.isLiveMethod = isLiveMethod;
this.desugaringMode = desugaringMode;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index e91925d..4310355 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -215,6 +215,9 @@
*/
@Override
public void scan(ProgramMethod context, CfInstructionDesugaringEventConsumer eventConsumer) {
+ if (desugaringMode == EMULATED_INTERFACE_ONLY) {
+ return;
+ }
if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
leavingStaticInvokeToInterface(context);
return;
@@ -274,6 +277,9 @@
if (invoke.isInvokeSpecial() && invoke.isInvokeConstructor(factory)) {
return DesugarDescription.nothing();
}
+ if (desugaringMode == EMULATED_INTERFACE_ONLY) {
+ return computeInvokeForEmulatedInterfaceOnly(invoke, context);
+ }
// If the invoke is not an interface invoke, then there should generally not be any desugaring.
// However, there are some cases where the insertion of forwarding methods can change behavior
// so we need to identify them at the various call sites here.
@@ -302,9 +308,6 @@
if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
return computeInvokeVirtualDispatch(holder, invoke, context);
}
- if (desugaringMode == EMULATED_INTERFACE_ONLY) {
- return DesugarDescription.nothing();
- }
if (invoke.isInvokeStatic()) {
return computeInvokeStatic(holder, invoke, context);
}
@@ -314,6 +317,51 @@
return DesugarDescription.nothing();
}
+ private DesugarDescription computeInvokeForEmulatedInterfaceOnly(
+ CfInvoke invoke, ProgramMethod context) {
+ if (!invoke.getMethod().getHolderType().isClassType()) {
+ return DesugarDescription.nothing();
+ }
+ DexClass holder = appView.definitionForHolder(invoke.getMethod(), context);
+ if (holder == null) {
+ return DesugarDescription.nothing();
+ }
+
+ if (!invoke.isInterface()) {
+ if (invoke.isInvokeSpecial()) {
+ return computeEmulatedInterfaceInvokeSpecial(holder, invoke.getMethod(), context);
+ }
+ if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+ DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
+ return description == null ? DesugarDescription.nothing() : description;
+ }
+ return DesugarDescription.nothing();
+ }
+
+ if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+ AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+ SingleResolutionResult<?> resolution =
+ appInfoForDesugaring
+ .resolveMethodLegacy(invoke.getMethod(), invoke.isInterface())
+ .asSingleResolution();
+ if (resolution != null
+ && resolution.getResolvedMethod().isPrivate()
+ && resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
+ return DesugarDescription.nothing();
+ }
+ if (resolution != null && resolution.getResolvedMethod().isStatic()) {
+ return DesugarDescription.nothing();
+ }
+ DesugarDescription description = computeEmulatedInterfaceVirtualDispatchOrNull(invoke);
+ return description != null ? description : DesugarDescription.nothing();
+ }
+
+ // Note: We do not rewrite invoke-static to emulated interfaces above api 24 and there is
+ // no way to encode it, but we could add the support here.
+
+ return DesugarDescription.nothing();
+ }
+
private DesugarDescription computeNonInterfaceInvoke(CfInvoke invoke, ProgramMethod context) {
assert !invoke.isInterface();
// Emulated interface desugaring will rewrite non-interface invokes.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index 1b3e48e..ad59051 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -56,14 +56,13 @@
// Short names to avoid creating long strings
public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
- private static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
- private static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX =
- NEST_ACCESS_NAME_PREFIX + "sm";
- private static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
- private static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
+ public static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
+ public static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "sm";
+ public static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
+ public static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
NEST_ACCESS_NAME_PREFIX + "sfget";
- private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
- private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
+ public static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
+ public static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
NEST_ACCESS_NAME_PREFIX + "sfput";
protected final AppView<?> appView;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 652c185..78eb343 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -23,13 +23,6 @@
// *) in vertical class merger,
// before moving a default interface method to its subtype, check if it does not collide with one
// in the given class hierarchy.
-// *) in uninstantiated type optimizer,
-// to avoid signature collisions while discarding unused return type or parameters.
-// *) in unused argument removal,
-// to avoid removing unused arguments from a virtual method if it is overriding another method or
-// being overridden by a method in a subtype, and to check that a virtual method after unused
-// argument removal does not collide with one in the existing class hierarchy.
-// TODO(b/66369976): to determine if a certain method can be made `final`.
public class MethodPoolCollection extends MemberPoolCollection<DexMethod> {
private final Predicate<DexEncodedMethod> methodTester;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 810f2de..81d408f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -4,9 +4,11 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+import static kotlinx.metadata.jvm.KotlinClassMetadata.Companion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -17,6 +19,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
@@ -26,13 +29,13 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
+import kotlin.Metadata;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmType;
import kotlinx.metadata.jvm.JvmClassExtensionVisitor;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmMethodSignature;
-import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public class KotlinClassInfo implements KotlinClassLevelInfo {
@@ -57,6 +60,8 @@
private final KotlinTypeInfo inlineClassUnderlyingType;
private final int jvmFlags;
private final String companionObjectName;
+ // Collection of context receiver types
+ private final List<KotlinTypeInfo> contextReceiverTypes;
// List of tracked assignments of kotlin metadata.
private final KotlinMetadataMembersTracker originalMembersWithKotlinInfo;
@@ -82,7 +87,8 @@
KotlinTypeInfo inlineClassUnderlyingType,
KotlinMetadataMembersTracker originalMembersWithKotlinInfo,
int jvmFlags,
- String companionObjectName) {
+ String companionObjectName,
+ List<KotlinTypeInfo> contextReceiverTypes) {
this.flags = flags;
this.name = name;
this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin =
@@ -105,6 +111,7 @@
this.originalMembersWithKotlinInfo = originalMembersWithKotlinInfo;
this.jvmFlags = jvmFlags;
this.companionObjectName = companionObjectName;
+ this.contextReceiverTypes = contextReceiverTypes;
}
public static KotlinClassInfo create(
@@ -188,7 +195,10 @@
KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
originalMembersWithKotlinInfo,
JvmExtensionsKt.getJvmFlags(kmClass),
- setCompanionObject(kmClass, hostClass, reporter));
+ setCompanionObject(kmClass, hostClass, reporter),
+ ListUtils.map(
+ kmClass.getContextReceiverTypes(),
+ contextRecieverType -> KotlinTypeInfo.create(contextRecieverType, factory, reporter)));
}
private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -279,7 +289,7 @@
}
@Override
- public Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+ public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
KmClass kmClass = new KmClass();
// TODO(b/154348683): Set flags.
kmClass.setFlags(flags);
@@ -414,6 +424,9 @@
rewritten |=
inlineClassUnderlyingType.rewrite(kmClass::visitInlineClassUnderlyingType, appView);
}
+ for (KotlinTypeInfo contextReceiverType : contextReceiverTypes) {
+ rewritten |= contextReceiverType.rewrite(kmClass::visitContextReceiverType, appView);
+ }
JvmClassExtensionVisitor extensionVisitor =
(JvmClassExtensionVisitor) kmClass.visitExtensions(JvmClassExtensionVisitor.TYPE);
extensionVisitor.visitJvmFlags(jvmFlags);
@@ -432,10 +445,8 @@
rewritten |=
localDelegatedProperties.rewrite(extensionVisitor::visitLocalDelegatedProperty, appView);
extensionVisitor.visitEnd();
- KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
- kmClass.accept(writer);
return Pair.create(
- writer.write().getHeader(),
+ Companion.writeClass(kmClass, getCompatibleKotlinInfo(), 0).getAnnotationData(),
rewritten || !originalMembersWithKotlinInfo.isEqual(rewrittenReferences, appView));
}
@@ -457,6 +468,7 @@
forEachApply(superTypes, type -> type::trace, definitionSupplier);
forEachApply(sealedSubClasses, sealedClass -> sealedClass::trace, definitionSupplier);
forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
+ forEachApply(contextReceiverTypes, nested -> nested::trace, definitionSupplier);
localDelegatedProperties.trace(definitionSupplier);
// TODO(b/154347404): trace enum entries.
if (anonymousObjectOrigin != null) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index e8f7fd0..d634e88 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Pair;
-import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlin.Metadata;
public interface KotlinClassLevelInfo extends EnqueuerMetadataTraceable {
@@ -56,7 +56,7 @@
return null;
}
- Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView);
+ Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView);
String getPackageName();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index eb07f4b..d02839a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -21,6 +21,7 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Consumer;
+import kotlin.Metadata;
import kotlinx.metadata.InconsistentKotlinMetadataException;
import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -142,8 +143,9 @@
KotlinClassMetadata kMetadata,
AppView<?> appView,
Consumer<DexEncodedMethod> keepByteCode) {
- String packageName = kMetadata.getHeader().getPackageName();
- int[] metadataVersion = kMetadata.getHeader().getMetadataVersion();
+ Metadata annotationData = kMetadata.getAnnotationData();
+ String packageName = annotationData.pn();
+ int[] metadataVersion = annotationData.mv();
if (kMetadata instanceof KotlinClassMetadata.Class) {
return KotlinClassInfo.create(
(KotlinClassMetadata.Class) kMetadata,
@@ -178,7 +180,7 @@
kotlin,
appView);
} else {
- throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
+ throw new MetadataError("unsupported 'k' value: " + annotationData.k());
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index 3daa33d..59e482f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -4,15 +4,17 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
+import static kotlinx.metadata.jvm.KotlinClassMetadata.Companion;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.Pair;
import java.util.function.Consumer;
+import kotlin.Metadata;
import kotlinx.metadata.KmPackage;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade;
// Holds information about Metadata.FileFacade
@@ -57,12 +59,12 @@
}
@Override
- public Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+ public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
KmPackage kmPackage = new KmPackage();
boolean rewritten = packageInfo.rewrite(kmPackage, clazz, appView);
- KotlinClassMetadata.FileFacade.Writer writer = new KotlinClassMetadata.FileFacade.Writer();
- kmPackage.accept(writer);
- return Pair.create(writer.write().getHeader(), rewritten);
+ return Pair.create(
+ Companion.writeFileFacade(kmPackage, getCompatibleKotlinInfo(), 0).getAnnotationData(),
+ rewritten);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index f5b40c2..69e4004 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import java.util.List;
import kotlinx.metadata.KmFunction;
@@ -41,6 +42,8 @@
private final KotlinContractInfo contract;
// A value describing if any of the parameters are crossinline.
private final boolean crossInlineParameter;
+ // Collection of context receiver types
+ private final List<KotlinTypeInfo> contextReceiverTypes;
private KotlinFunctionInfo(
int flags,
@@ -53,7 +56,8 @@
KotlinTypeReference lambdaClassOrigin,
KotlinVersionRequirementInfo versionRequirements,
KotlinContractInfo contract,
- boolean crossInlineParameter) {
+ boolean crossInlineParameter,
+ List<KotlinTypeInfo> contextReceiverTypes) {
this.flags = flags;
this.name = name;
this.returnType = returnType;
@@ -65,6 +69,7 @@
this.versionRequirements = versionRequirements;
this.contract = contract;
this.crossInlineParameter = crossInlineParameter;
+ this.contextReceiverTypes = contextReceiverTypes;
}
public boolean hasCrossInlineParameter() {
@@ -98,7 +103,10 @@
getlambdaClassOrigin(kmFunction, factory),
KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
KotlinContractInfo.create(kmFunction.getContract(), factory, reporter),
- isCrossInline);
+ isCrossInline,
+ ListUtils.map(
+ kmFunction.getContextReceiverTypes(),
+ contextRecieverType -> KotlinTypeInfo.create(contextRecieverType, factory, reporter)));
}
private static KotlinTypeReference getlambdaClassOrigin(
@@ -141,6 +149,9 @@
for (KotlinTypeParameterInfo typeParameterInfo : typeParameters) {
rewritten |= typeParameterInfo.rewrite(kmFunction::visitTypeParameter, appView);
}
+ for (KotlinTypeInfo contextReceiverType : contextReceiverTypes) {
+ rewritten |= contextReceiverType.rewrite(kmFunction::visitContextReceiverType, appView);
+ }
if (receiverParameterType != null) {
rewritten |= receiverParameterType.rewrite(kmFunction::visitReceiverParameterType, appView);
}
@@ -187,6 +198,7 @@
receiverParameterType.trace(definitionSupplier);
}
forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+ forEachApply(contextReceiverTypes, type -> type::trace, definitionSupplier);
if (signature != null) {
signature.trace(definitionSupplier);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java
new file mode 100644
index 0000000..a011e63
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataAnnotationWrapper.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+/***
+ * This is a wrapper around kotlin.Metadata needed for tests to access the internal data. The need
+ * for the wrapper comes from R8 relocating kotlin.* to com.android.tools.r8.kotlin.* in R8lib but
+ * not in tests, so kotlin.Metadata cannot cross the boundary.
+ */
+public class KotlinMetadataAnnotationWrapper {
+
+ private final kotlin.Metadata metadata;
+
+ private KotlinMetadataAnnotationWrapper(kotlin.Metadata metadata) {
+ this.metadata = metadata;
+ }
+
+ public static KotlinMetadataAnnotationWrapper wrap(KotlinClassMetadata classMetadata) {
+ return new KotlinMetadataAnnotationWrapper(classMetadata.getAnnotationData());
+ }
+
+ public int kind() {
+ return metadata.k();
+ }
+
+ public String[] data1() {
+ return metadata.d1();
+ }
+
+ public String[] data2() {
+ return metadata.d2();
+ }
+
+ public String packageName() {
+ return metadata.pn();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index c06aea8..e57fc63 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -27,7 +27,6 @@
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
-import kotlinx.metadata.jvm.KotlinClassHeader;
public class KotlinMetadataRewriter {
@@ -155,17 +154,16 @@
DexAnnotation oldMeta,
WriteMetadataFieldInfo writeMetadataFieldInfo) {
try {
- Pair<KotlinClassHeader, Boolean> kotlinClassHeader = kotlinInfo.rewrite(clazz, appView);
+ Pair<kotlin.Metadata, Boolean> kotlinMetadata = kotlinInfo.rewrite(clazz, appView);
// TODO(b/185756596): Remove when special handling is no longer needed.
- if (!kotlinClassHeader.getSecond()
- && appView.options().testing.keepMetadataInR8IfNotRewritten) {
+ if (!kotlinMetadata.getSecond() && appView.options().testing.keepMetadataInR8IfNotRewritten) {
// No rewrite occurred and the data is the same as before.
assert appView.checkForTesting(
() ->
verifyRewrittenMetadataIsEquivalent(
clazz.annotations().getFirstMatching(factory.kotlinMetadataType),
createKotlinMetadataAnnotation(
- kotlinClassHeader.getFirst(),
+ kotlinMetadata.getFirst(),
kotlinInfo.getPackageName(),
getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
writeMetadataFieldInfo)));
@@ -173,7 +171,7 @@
}
DexAnnotation newMeta =
createKotlinMetadataAnnotation(
- kotlinClassHeader.getFirst(),
+ kotlinMetadata.getFirst(),
kotlinInfo.getPackageName(),
getMaxVersion(METADATA_VERSION_1_4, kotlinInfo.getMetadataVersion()),
writeMetadataFieldInfo);
@@ -222,7 +220,7 @@
}
private DexAnnotation createKotlinMetadataAnnotation(
- KotlinClassHeader header,
+ kotlin.Metadata metadata,
String packageName,
int[] metadataVersion,
WriteMetadataFieldInfo writeMetadataFieldInfo) {
@@ -234,31 +232,30 @@
}
if (writeMetadataFieldInfo.writeKind) {
elements.add(
- new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
+ new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(metadata.k())));
}
if (writeMetadataFieldInfo.writeData1) {
elements.add(
- new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
+ new DexAnnotationElement(kotlin.metadata.data1, createStringArray(metadata.d1())));
}
if (writeMetadataFieldInfo.writeData2) {
elements.add(
- new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
+ new DexAnnotationElement(kotlin.metadata.data2, createStringArray(metadata.d2())));
}
if (writeMetadataFieldInfo.writePackageName && packageName != null && !packageName.isEmpty()) {
elements.add(
new DexAnnotationElement(
kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
}
- if (writeMetadataFieldInfo.writeExtraString && !header.getExtraString().isEmpty()) {
+ if (writeMetadataFieldInfo.writeExtraString && !metadata.xs().isEmpty()) {
elements.add(
new DexAnnotationElement(
kotlin.metadata.extraString,
- new DexValueString(factory.createString(header.getExtraString()))));
+ new DexValueString(factory.createString(metadata.xs()))));
}
- if (writeMetadataFieldInfo.writeExtraInt && header.getExtraInt() != 0) {
+ if (writeMetadataFieldInfo.writeExtraInt && metadata.xi() != 0) {
elements.add(
- new DexAnnotationElement(
- kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+ new DexAnnotationElement(kotlin.metadata.extraInt, DexValueInt.create(metadata.xi())));
}
DexEncodedAnnotation encodedAnnotation =
new DexEncodedAnnotation(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index d5884f5..ccc4938 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Pair;
import com.google.common.base.Strings;
+import kotlin.Metadata;
import kotlinx.metadata.KmExtensionType;
import kotlinx.metadata.KmProperty;
import kotlinx.metadata.KmPropertyExtensionVisitor;
@@ -28,7 +29,7 @@
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmMethodSignature;
import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
-import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
public class KotlinMetadataUtils {
@@ -50,7 +51,7 @@
}
@Override
- public Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+ public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
throw new Unreachable("Should never be called");
}
@@ -232,4 +233,8 @@
}
return DescriptorUtils.descriptorToKotlinClassifier(descriptor);
}
+
+ static int[] getCompatibleKotlinInfo() {
+ return KotlinClassMetadata.COMPATIBLE_METADATA_VERSION;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index 1e4d633..dfb5d76 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -379,20 +379,31 @@
indent,
"constructors",
sb,
- newIndent -> {
- appendKmList(
- newIndent,
- "KmConstructor",
- sb,
- kmClass.getConstructors().stream()
- .sorted(
- Comparator.comparing(
- kmConstructor -> JvmExtensionsKt.getSignature(kmConstructor).asString()))
- .collect(Collectors.toList()),
- (nextIndent, constructor) -> {
- appendKmConstructor(nextIndent, sb, constructor);
- });
- });
+ newIndent ->
+ appendKmList(
+ newIndent,
+ "KmConstructor",
+ sb,
+ kmClass.getConstructors().stream()
+ .sorted(
+ Comparator.comparing(
+ kmConstructor ->
+ JvmExtensionsKt.getSignature(kmConstructor).asString()))
+ .collect(Collectors.toList()),
+ (nextIndent, constructor) -> {
+ appendKmConstructor(nextIndent, sb, constructor);
+ }));
+ appendKeyValue(
+ indent,
+ "contextReceiverTypes",
+ sb,
+ newIndent ->
+ appendKmList(
+ newIndent,
+ "KmType",
+ sb,
+ kmClass.getContextReceiverTypes(),
+ (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
appendKmDeclarationContainer(indent, sb, kmClass);
}
@@ -458,6 +469,17 @@
appendKmContract(nextIndent, sb, contract);
});
}
+ appendKeyValue(
+ newIndent,
+ "contextReceiverTypes",
+ sb,
+ newNewIndent ->
+ appendKmList(
+ newNewIndent,
+ "KmType",
+ sb,
+ function.getContextReceiverTypes(),
+ (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
appendKeyValue(
newIndent, "signature", sb, signature != null ? signature.asString() : "null");
@@ -500,6 +522,17 @@
sb,
nextIndent -> appendValueParameter(nextIndent, sb, kmProperty.getSetterParameter()));
appendKmVersionRequirement(newIndent, sb, kmProperty.getVersionRequirements());
+ appendKeyValue(
+ newIndent,
+ "contextReceiverTypes",
+ sb,
+ newNewIndent ->
+ appendKmList(
+ newNewIndent,
+ "KmType",
+ sb,
+ kmProperty.getContextReceiverTypes(),
+ (nextIndent, kmType) -> appendKmType(nextIndent, sb, kmType)));
appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
appendKeyValue(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 2a89998..7f0b67f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+import static kotlinx.metadata.jvm.KotlinClassMetadata.Companion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -14,8 +16,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
+import kotlin.Metadata;
import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassFacade;
// Holds information about Metadata.MultiFileClassFace
@@ -55,7 +56,7 @@
}
@Override
- public Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+ public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
List<String> partClassNameStrings = new ArrayList<>(partClassNames.size());
boolean rewritten = false;
for (KotlinTypeReference partClassName : partClassNames) {
@@ -69,9 +70,10 @@
appView,
null);
}
- KotlinClassMetadata.MultiFileClassFacade.Writer writer =
- new KotlinClassMetadata.MultiFileClassFacade.Writer();
- return Pair.create(writer.write(partClassNameStrings).getHeader(), rewritten);
+ return Pair.create(
+ Companion.writeMultiFileClassFacade(partClassNameStrings, getCompatibleKotlinInfo(), 0)
+ .getAnnotationData(),
+ rewritten);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index 62460ae..a51defc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -4,15 +4,17 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
+import static kotlinx.metadata.jvm.KotlinClassMetadata.Companion;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.Pair;
import java.util.function.Consumer;
+import kotlin.Metadata;
import kotlinx.metadata.KmPackage;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
import kotlinx.metadata.jvm.KotlinClassMetadata.MultiFileClassPart;
// Holds information about Metadata.MultiFileClassPartInfo
@@ -63,13 +65,13 @@
}
@Override
- public Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+ public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
KmPackage kmPackage = new KmPackage();
boolean rewritten = packageInfo.rewrite(kmPackage, clazz, appView);
- KotlinClassMetadata.MultiFileClassPart.Writer writer =
- new KotlinClassMetadata.MultiFileClassPart.Writer();
- kmPackage.accept(writer);
- return Pair.create(writer.write(facadeClassName).getHeader(), rewritten);
+ return Pair.create(
+ Companion.writeMultiFileClassPart(kmPackage, facadeClassName, getCompatibleKotlinInfo(), 0)
+ .getAnnotationData(),
+ rewritten);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 6d10139..a11b9a9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import java.util.List;
import kotlinx.metadata.KmProperty;
@@ -58,6 +59,8 @@
private final KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations;
private final KotlinJvmMethodSignatureInfo syntheticMethodForDelegate;
+ // Collection of context receiver types
+ private final List<KotlinTypeInfo> contextReceiverTypes;
private KotlinPropertyInfo(
int flags,
@@ -74,7 +77,8 @@
KotlinJvmMethodSignatureInfo getterSignature,
KotlinJvmMethodSignatureInfo setterSignature,
KotlinJvmMethodSignatureInfo syntheticMethodForAnnotations,
- KotlinJvmMethodSignatureInfo syntheticMethodForDelegate) {
+ KotlinJvmMethodSignatureInfo syntheticMethodForDelegate,
+ List<KotlinTypeInfo> contextReceiverTypes) {
this.flags = flags;
this.getterFlags = getterFlags;
this.setterFlags = setterFlags;
@@ -90,6 +94,7 @@
this.setterSignature = setterSignature;
this.syntheticMethodForAnnotations = syntheticMethodForAnnotations;
this.syntheticMethodForDelegate = syntheticMethodForDelegate;
+ this.contextReceiverTypes = contextReceiverTypes;
}
public static KotlinPropertyInfo create(
@@ -113,7 +118,10 @@
KotlinJvmMethodSignatureInfo.create(
JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), factory),
KotlinJvmMethodSignatureInfo.create(
- JvmExtensionsKt.getSyntheticMethodForDelegate(kmProperty), factory));
+ JvmExtensionsKt.getSyntheticMethodForDelegate(kmProperty), factory),
+ ListUtils.map(
+ kmProperty.getContextReceiverTypes(),
+ contextRecieverType -> KotlinTypeInfo.create(contextRecieverType, factory, reporter)));
}
@Override
@@ -160,6 +168,9 @@
for (KotlinTypeParameterInfo typeParameter : typeParameters) {
rewritten |= typeParameter.rewrite(kmProperty::visitTypeParameter, appView);
}
+ for (KotlinTypeInfo contextReceiverType : contextReceiverTypes) {
+ rewritten |= contextReceiverType.rewrite(kmProperty::visitContextReceiverType, appView);
+ }
rewritten |= versionRequirements.rewrite(kmProperty::visitVersionRequirement);
JvmPropertyExtensionVisitor extensionVisitor =
(JvmPropertyExtensionVisitor) kmProperty.visitExtensions(JvmPropertyExtensionVisitor.TYPE);
@@ -207,6 +218,7 @@
setterParameter.trace(definitionSupplier);
}
forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+ forEachApply(contextReceiverTypes, type -> type::trace, definitionSupplier);
if (fieldSignature != null) {
fieldSignature.trace(definitionSupplier);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index fb9b55f..a0783ed 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -4,14 +4,16 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getCompatibleKotlinInfo;
+import static kotlinx.metadata.jvm.KotlinClassMetadata.Companion;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.utils.Pair;
+import kotlin.Metadata;
import kotlinx.metadata.KmLambda;
-import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass;
-import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass.Writer;
// Holds information about a Metadata.SyntheticClass object.
public class KotlinSyntheticClassInfo implements KotlinClassLevelInfo {
@@ -73,15 +75,16 @@
}
@Override
- public Pair<KotlinClassHeader, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
- Writer writer = new Writer();
- boolean rewritten = false;
- if (lambda != null) {
- KmLambda kmLambda = new KmLambda();
- rewritten = lambda.rewrite(() -> kmLambda, clazz, appView);
- kmLambda.accept(writer);
+ public Pair<Metadata, Boolean> rewrite(DexClass clazz, AppView<?> appView) {
+ if (lambda == null) {
+ return Pair.create(
+ Companion.writeSyntheticClass(getCompatibleKotlinInfo(), 0).getAnnotationData(), false);
}
- return Pair.create(writer.write().getHeader(), rewritten);
+ KmLambda kmLambda = new KmLambda();
+ boolean rewritten = lambda.rewrite(() -> kmLambda, clazz, appView);
+ return Pair.create(
+ Companion.writeLambda(kmLambda, getCompatibleKotlinInfo(), 0).getAnnotationData(),
+ rewritten);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index e83cff6..a20d86c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
@@ -86,7 +85,6 @@
boolean rewrite(KmVisitorProviders.KmTypeVisitorProvider visitorProvider, AppView<?> appView) {
// TODO(b/154348683): Check for correct flags
KmTypeVisitor kmTypeVisitor = visitorProvider.get(flags);
- NamingLens namingLens = appView.getNamingLens();
boolean rewritten = classifier.rewrite(kmTypeVisitor, appView);
if (abbreviatedType != null) {
rewritten |= abbreviatedType.rewrite(kmTypeVisitor::visitAbbreviatedType, appView);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index b9bc6bc..508c5db 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -142,14 +142,6 @@
CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
}
- // TODO(b/241763080): Remove when 2.2 is stable.
- @Deprecated
- public static ClassNameMapper mapperFromStringWithExperimental(String contents)
- throws IOException {
- return mapperFromBufferedReader(
- CharSource.wrap(contents).openBufferedStream(), null, false, true, true);
- }
-
public static ClassNameMapper mapperFromString(
String contents,
DiagnosticsHandler diagnosticsHandler,
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/UnknownJsonMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/UnknownJsonMappingInformation.java
index 98699b2..3e061db 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/UnknownJsonMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/UnknownJsonMappingInformation.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.naming.mappinginformation;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.MappingComposeException;
import com.android.tools.r8.naming.mappinginformation.MappingInformation.PositionalMappingInformation;
import com.google.gson.JsonObject;
@@ -31,7 +30,7 @@
@Override
public String serialize() {
- throw new Unreachable("We should not at this point serialize unknown information");
+ return payload;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index 11d080b..f3d4f1d 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -34,7 +34,6 @@
private final List<ArtProfileRule> rules;
ArtProfile(List<ArtProfileRule> rules) {
- assert !rules.isEmpty();
this.rules = rules;
}
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 cb80e54..f7d7554 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -475,19 +475,8 @@
futures.add(
ThreadUtils.processAsynchronously(
() -> {
- Set<DexField> removedFields = prunedItems.getRemovedFields();
- Set<DexMethod> removedMethods = prunedItems.getRemovedMethods();
- if (map.size() <= removedFields.size() + removedMethods.size()) {
- map.keySet()
- .removeIf(
- member ->
- member.isDexField()
- ? removedFields.contains(member.asDexField())
- : removedMethods.contains(member.asDexMethod()));
- } else {
- removedFields.forEach(map::remove);
- removedMethods.forEach(map::remove);
- }
+ prunedItems.getRemovedFields().forEach(map::removeBoolean);
+ prunedItems.getRemovedMethods().forEach(map::removeBoolean);
},
executorService));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 57726f6..4a4609f 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -19,12 +20,14 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
@@ -46,6 +49,8 @@
private final AppView<AppInfoWithLiveness> appView;
private final TreePrunerConfiguration configuration;
private final UnusedItemsPrinter unusedItemsPrinter;
+ private final Set<DexField> prunedFields = Sets.newIdentityHashSet();
+ private final Set<DexMethod> prunedMethods = Sets.newIdentityHashSet();
private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
@@ -66,21 +71,32 @@
: UnusedItemsPrinter.DONT_PRINT;
}
- public DirectMappedDexApplication run(ExecutorService executorService) throws ExecutionException {
+ public PrunedItems run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ return run(executorService, timing, Collections.emptySet());
+ }
+
+ public PrunedItems run(ExecutorService executorService, Timing timing, Set<DexType> prunedTypes)
+ throws ExecutionException {
+ return timing.time("Pruning application...", () -> internalRun(executorService, prunedTypes));
+ }
+
+ private PrunedItems internalRun(
+ ExecutorService executorService, Set<DexType> previouslyPrunedTypes)
+ throws ExecutionException {
DirectMappedDexApplication application = appView.appInfo().app().asDirect();
- Timing timing = application.timing;
- timing.begin("Pruning application...");
- try {
DirectMappedDexApplication.Builder builder = removeUnused(application);
DirectMappedDexApplication newApplication =
prunedTypes.isEmpty() && !appView.options().configurationDebugging
? application
: builder.build();
fixupOptimizationInfo(newApplication, executorService);
- return newApplication;
- } finally {
- timing.end();
- }
+ return PrunedItems.builder()
+ .setPrunedApp(newApplication)
+ .addRemovedClasses(CollectionUtils.mergeSets(previouslyPrunedTypes, prunedTypes))
+ .addRemovedFields(prunedFields)
+ .addRemovedMethods(prunedMethods)
+ .addAdditionalPinnedItems(methodsToKeepForConfigurationDebugging)
+ .build();
}
private DirectMappedDexApplication.Builder removeUnused(DirectMappedDexApplication application) {
@@ -342,6 +358,7 @@
Log.debug(getClass(), "Removing method %s.", method.getReference());
}
unusedItemsPrinter.registerUnusedMethod(method);
+ prunedMethods.add(method.getReference());
}
}
return reachableMethods.isEmpty()
@@ -367,7 +384,9 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Removing field %s.", fields.get(firstUnreachable));
}
- unusedItemsPrinter.registerUnusedField(fields.get(firstUnreachable));
+ DexEncodedField firstUnreachableField = fields.get(firstUnreachable);
+ unusedItemsPrinter.registerUnusedField(firstUnreachableField);
+ prunedFields.add(firstUnreachableField.getReference());
ArrayList<DexEncodedField> reachableOrReferencedFields = new ArrayList<>(fields.size());
for (int i = 0; i < firstUnreachable; i++) {
reachableOrReferencedFields.add(fields.get(i));
@@ -381,6 +400,7 @@
Log.debug(getClass(), "Removing field %s.", field.getReference());
}
unusedItemsPrinter.registerUnusedField(field);
+ prunedFields.add(field.getReference());
}
}
return reachableOrReferencedFields.isEmpty()
@@ -388,14 +408,6 @@
: reachableOrReferencedFields.toArray(DexEncodedField.EMPTY_ARRAY);
}
- public Set<DexType> getRemovedClasses() {
- return Collections.unmodifiableSet(prunedTypes);
- }
-
- public Collection<DexMethod> getMethodsToKeepForConfigurationDebugging() {
- return Collections.unmodifiableCollection(methodsToKeepForConfigurationDebugging);
- }
-
private void fixupOptimizationInfo(
DirectMappedDexApplication application, ExecutorService executorService)
throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
index 072022d..0ee9a72 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassReferenceUtils.java
@@ -45,6 +45,10 @@
}
}
+ public static String toSmaliString(ClassReference classReference) {
+ return classReference.getDescriptor();
+ }
+
public static DexType toDexType(ClassReference classReference, DexItemFactory dexItemFactory) {
return dexItemFactory.createType(classReference.getDescriptor());
}
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 b95ac53..d49f628 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -68,6 +68,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.nest.Nest;
import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapVersion;
@@ -819,7 +820,7 @@
new CallSiteOptimizationOptions();
private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
private final ClassInlinerOptions classInlinerOptions = new ClassInlinerOptions();
- private final InlinerOptions inlinerOptions = new InlinerOptions();
+ private final InlinerOptions inlinerOptions = new InlinerOptions(this);
private final HorizontalClassMergerOptions horizontalClassMergerOptions =
new HorizontalClassMergerOptions();
private final OpenClosedInterfacesOptions openClosedInterfacesOptions =
@@ -1528,7 +1529,7 @@
boolean test(AppView<?> appView, ProgramMethod method, int inliningDepth);
}
- public class InlinerOptions {
+ public static class InlinerOptions {
public boolean enableInlining =
!parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.disableinlining", false);
@@ -1559,17 +1560,31 @@
public ApplyInliningToInlineePredicate applyInliningToInlineePredicateForTesting = null;
+ private final InternalOptions options;
+
+ public InlinerOptions(InternalOptions options) {
+ this.options = options;
+ }
+
+ public static void disableInlining(InternalOptions options) {
+ options.inlinerOptions().enableInlining = false;
+ }
+
+ public static void setOnlyForceInlining(InternalOptions options) {
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ }
+
public int getSimpleInliningInstructionLimit() {
// If a custom simple inlining instruction limit is set, then use that.
if (simpleInliningInstructionLimit >= 0) {
return simpleInliningInstructionLimit;
}
// Allow 3 instructions when generating to class files.
- if (isGeneratingClassFiles()) {
+ if (options.isGeneratingClassFiles()) {
return 3;
}
// Allow the size of the dex code to be up to 5 bytes.
- assert isGeneratingDex();
+ assert options.isGeneratingDex();
return 5;
}
@@ -1578,7 +1593,7 @@
if (applyInliningToInlineePredicateForTesting != null) {
return applyInliningToInlineePredicateForTesting.test(appView, inlinee, inliningDepth);
}
- if (protoShrinking.shouldApplyInliningToInlinee(appView, inlinee, inliningDepth)) {
+ if (options.protoShrinking().shouldApplyInliningToInlinee(appView, inlinee, inliningDepth)) {
return true;
}
return false;
@@ -1785,8 +1800,8 @@
}
public static class MappingComposeOptions {
- // TODO(b/241763080): Remove when enabled.
public boolean enableExperimentalMappingComposition = false;
+
// TODO(b/247136434): Disable for internal builds.
public boolean allowNonExistingOriginalRanges = true;
public Consumer<ClassNameMapper> generatedClassNameMapperConsumer = null;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index 11f01d2..b961f22 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -53,11 +53,12 @@
if (appView.options().mappingComposeOptions().generatedClassNameMapperConsumer != null) {
appView.options().mappingComposeOptions().generatedClassNameMapperConsumer.accept(mapper);
}
- if (appView.options().mappingComposeOptions().enableExperimentalMappingComposition) {
+ if (appView.options().mappingComposeOptions().enableExperimentalMappingComposition
+ && appView.appInfo().app().getProguardMap() != null) {
timing.begin("Proguard map composition");
try {
mapper =
- ClassNameMapper.mapperFromStringWithExperimental(
+ ClassNameMapper.mapperFromStringWithPreamble(
MappingComposer.compose(
appView.options(), appView.appInfo().app().getProguardMap(), mapper));
} catch (IOException | MappingComposeException e) {
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index de0565f..c8c21fa 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -9,12 +9,16 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.profile.art.ArtProfileConsumer;
import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -32,6 +36,7 @@
private StringBuilder proguardMapOutputBuilder = null;
private boolean enableMissingLibraryApiModeling = true;
+ private List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
@Override
public boolean isD8TestBuilder() {
@@ -134,6 +139,16 @@
return self();
}
+ public D8TestBuilder addArtProfileForRewriting(ArtProfileProvider artProfileProvider) {
+ return addArtProfileForRewriting(
+ artProfileProvider,
+ ArtProfileTestingUtils.createResidualArtProfileConsumer(residualArtProfiles::add));
+ }
+
+ public D8TestBuilder addArtProfileForRewriting(ExternalArtProfile artProfile) {
+ return addArtProfileForRewriting(ArtProfileTestingUtils.createArtProfileProvider(artProfile));
+ }
+
public D8TestBuilder addArtProfileForRewriting(
ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
builder.addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer);
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index 36f3b07..259f1a0 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MAX_SUPPORTED_VERSION;
import static com.android.tools.r8.ToolHelper.isWindows;
+import static com.google.common.io.Files.getNameWithoutExtension;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -223,6 +224,10 @@
return this;
}
+ public KotlinCompilerTool enableExperimentalContextReceivers() {
+ return addArguments("-Xcontext-receivers");
+ }
+
public KotlinCompilerTool addSourceFiles(Path... files) {
return addSourceFiles(Arrays.asList(files));
}
@@ -268,7 +273,8 @@
try {
// The Kotlin compiler does not require particular naming of files except for
// the extension, so just create a file called source.kt in a new directory.
- Path fileNamedKt = temp.newFolder().toPath().resolve("source.kt");
+ String newFileName = getNameWithoutExtension(fileNotNamedKt.toString()) + ".kt";
+ Path fileNamedKt = temp.newFolder().toPath().resolve(newFileName);
Files.copy(fileNotNamedKt, fileNamedKt);
return fileNamedKt;
} catch (IOException e) {
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index bf1ad43..9fb85b3 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -59,6 +59,10 @@
return result.stderr;
}
+ public String getProguardMap() {
+ return proguardMap;
+ }
+
@Override
public CodeInspector inspector() throws IOException {
return new CodeInspector(app, proguardMap);
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index a844c1f..34f8a0b 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.profile.art.ArtProfileConsumer;
import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
import com.android.tools.r8.shaking.CheckEnumUnboxedRule;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.KeepUnusedReturnValueRule;
@@ -68,6 +70,7 @@
private boolean allowUnusedProguardConfigurationRules = false;
private boolean enableMissingLibraryApiModeling = true;
private CollectingGraphConsumer graphConsumer = null;
+ private List<ExternalArtProfile> residualArtProfiles = new ArrayList<>();
private List<String> keepRules = new ArrayList<>();
private List<Path> mainDexRulesFiles = new ArrayList<>();
private List<String> applyMappingMaps = new ArrayList<>();
@@ -158,7 +161,8 @@
createDefaultProguardMapConsumer ? proguardMapBuilder.toString() : null,
graphConsumer,
getMinApiLevel(),
- features);
+ features,
+ residualArtProfiles);
switch (allowedDiagnosticMessages) {
case ALL:
compileResult.getDiagnosticMessages().assertAllDiagnosticsMatch(new IsAnything<>());
@@ -782,6 +786,16 @@
return self();
}
+ public T addArtProfileForRewriting(ArtProfileProvider artProfileProvider) {
+ return addArtProfileForRewriting(
+ artProfileProvider,
+ ArtProfileTestingUtils.createResidualArtProfileConsumer(residualArtProfiles::add));
+ }
+
+ public T addArtProfileForRewriting(ExternalArtProfile artProfile) {
+ return addArtProfileForRewriting(ArtProfileTestingUtils.createArtProfileProvider(artProfile));
+ }
+
public T addArtProfileForRewriting(
ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
builder.addArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer);
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 688ab43..b3b6e31 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -9,11 +9,14 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -22,6 +25,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
@@ -31,6 +35,7 @@
private final String proguardMap;
private final CollectingGraphConsumer graphConsumer;
private final List<Path> features;
+ private final List<ExternalArtProfile> residualArtProfiles;
R8TestCompileResult(
TestState state,
@@ -42,13 +47,15 @@
String proguardMap,
CollectingGraphConsumer graphConsumer,
int minApiLevel,
- List<Path> features) {
+ List<Path> features,
+ List<ExternalArtProfile> residualArtProfiles) {
super(state, app, minApiLevel, outputMode, libraryDesugaringTestConfiguration);
this.proguardConfiguration = proguardConfiguration;
this.syntheticProguardRules = syntheticProguardRules;
this.proguardMap = proguardMap;
this.graphConsumer = graphConsumer;
this.features = features;
+ this.residualArtProfiles = residualArtProfiles;
}
@Override
@@ -124,6 +131,19 @@
return self();
}
+ public <E extends Throwable> R8TestCompileResult inspectResidualArtProfile(
+ ThrowingConsumer<ArtProfileInspector, E> consumer) throws E, IOException, ExecutionException {
+ return inspectResidualArtProfile(
+ (rewrittenArtProfile, inspector) -> consumer.accept(rewrittenArtProfile));
+ }
+
+ public <E extends Throwable> R8TestCompileResult inspectResidualArtProfile(
+ ThrowingBiConsumer<ArtProfileInspector, CodeInspector, E> consumer) throws E, IOException {
+ assertEquals(1, residualArtProfiles.size());
+ consumer.accept(new ArtProfileInspector(residualArtProfiles.iterator().next()), inspector());
+ return self();
+ }
+
public GraphInspector graphInspector() throws IOException {
assert graphConsumer != null;
return new GraphInspector(graphConsumer, inspector());
diff --git a/src/test/java/com/android/tools/r8/SanityCheck.java b/src/test/java/com/android/tools/r8/SanityCheck.java
index b82995c..38fa873 100644
--- a/src/test/java/com/android/tools/r8/SanityCheck.java
+++ b/src/test/java/com/android/tools/r8/SanityCheck.java
@@ -29,7 +29,7 @@
private static final String SRV_PREFIX = "META-INF/services/";
private static final String METADATA_EXTENSION =
- "com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions";
+ "com.android.tools.r8.jetbrains.kotlinx.metadata.internal.extensions.MetadataExtensions";
private static final String EXT_IN_SRV = SRV_PREFIX + METADATA_EXTENSION;
private void checkJarContent(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index b487caf..8b2877f 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -205,7 +205,7 @@
};
}
- static ApiModelingClassVerificationHelper verifyThat(
+ public static ApiModelingClassVerificationHelper verifyThat(
CodeInspector inspector, TestParameters parameters, Class<?> clazz) {
return new ApiModelingClassVerificationHelper(inspector, parameters, clazz);
}
@@ -295,7 +295,7 @@
hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsInstanceOf);
}
- void hasConstClassOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+ public void hasConstClassOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
hasConstClassOutlinedFrom(method);
} else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index b357b2c..a33e2d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -221,6 +221,9 @@
directory2.toString()
});
List<String> html = Files.readAllLines(directory2.resolve("apis.html"));
- assertTrue(html.contains(" <td><code>java.util.function</code></td>"));
+ // The doc has the same content than the lint data that is tested above, this is just a sanity
+ // check that the doc generation ran without error and looks sane.
+ assertEquals("<tr>", html.get(0));
+ assertEquals("</tr>", html.get(html.size() - 2));
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java
index 6f1b750..bd5554af 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/CollectionToArrayTest.java
@@ -18,6 +18,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -53,6 +54,7 @@
this.compilationSpecification = compilationSpecification;
}
+ @Ignore("b/266401747")
@Test
public void test() throws Throwable {
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InterfacePartialDesugTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InterfacePartialDesugTest.java
new file mode 100644
index 0000000..a417783
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InterfacePartialDesugTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import java.util.function.Supplier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InterfacePartialDesugTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(),
+ ImmutableList.of(JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public InterfacePartialDesugTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addProgramClassFileData(getTransforms())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42", "0");
+ }
+
+ private List<byte[]> getTransforms() throws IOException, NoSuchMethodException {
+ return ImmutableList.of(
+ transformer(Itf.class)
+ .setAccessFlags(
+ Itf.class.getDeclaredMethod("privateRun", Supplier.class),
+ flags -> {
+ flags.unsetPublic();
+ flags.setPrivate();
+ })
+ .setAccessFlags(
+ Itf.class.getDeclaredMethod("privateGet"),
+ flags -> {
+ flags.unsetPublic();
+ flags.setPrivate();
+ })
+ .transform());
+ }
+
+ public static class Main implements Itf {
+
+ public static void main(String[] args) {
+ System.out.println(new Main().get());
+ // We need to check a call to clone is correctly non desugared.
+ System.out.println((new Main[0]).clone().length);
+ }
+ }
+
+ public interface Itf {
+
+ default Object get() {
+ return (privateRun(this::privateGet));
+ }
+
+ // Method will be private at runtime.
+ default Object privateRun(Supplier<Object> getter) {
+ return getter.get();
+ }
+
+ // Method will be private at runtime.
+ default Object privateGet() {
+ return 42;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index 4144aaa..ed81c32 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -27,6 +27,8 @@
import com.android.tools.r8.profile.art.ArtProfileConsumer;
import com.android.tools.r8.profile.art.ArtProfileForRewriting;
import com.android.tools.r8.profile.art.ArtProfileProvider;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
import com.android.tools.r8.tracereferences.TraceReferences;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.FileUtils;
@@ -58,6 +60,7 @@
private CustomLibrarySpecification customLibrarySpecification = null;
private TestingKeepRuleConsumer keepRuleConsumer = null;
+ private List<ExternalArtProfile> l8ResidualArtProfiles = new ArrayList<>();
public DesugaredLibraryTestBuilder(
T test,
@@ -378,7 +381,8 @@
libraryDesugaringSpecification,
compilationSpecification,
customLibCompile,
- l8Compile);
+ l8Compile,
+ l8ResidualArtProfiles);
}
private D8TestCompileResult compileCustomLib() throws CompilationFailedException {
@@ -501,7 +505,18 @@
return this;
}
- public DesugaredLibraryTestBuilder<?> addL8ArtProfileForRewriting(
+ public DesugaredLibraryTestBuilder<T> addL8ArtProfileForRewriting(
+ ArtProfileProvider artProfileProvider) {
+ return addL8ArtProfileForRewriting(
+ artProfileProvider,
+ ArtProfileTestingUtils.createResidualArtProfileConsumer(l8ResidualArtProfiles::add));
+ }
+
+ public DesugaredLibraryTestBuilder<T> addL8ArtProfileForRewriting(ExternalArtProfile artProfile) {
+ return addL8ArtProfileForRewriting(ArtProfileTestingUtils.createArtProfileProvider(artProfile));
+ }
+
+ public DesugaredLibraryTestBuilder<T> addL8ArtProfileForRewriting(
ArtProfileProvider artProfileProvider, ArtProfileConsumer residualArtProfileConsumer) {
l8ArtProfilesForRewriting.add(
new ArtProfileForRewriting(artProfileProvider, residualArtProfileConsumer));
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestCompileResult.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestCompileResult.java
index 3172876..d49f2ef 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestCompileResult.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.desugar.desugaredlibrary.test;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.L8TestCompileResult;
@@ -13,6 +15,9 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
@@ -30,6 +35,7 @@
private final CompilationSpecification compilationSpecification;
private final D8TestCompileResult customLibCompile;
private final L8TestCompileResult l8Compile;
+ private final List<ExternalArtProfile> l8ResidualArtProfiles;
// In case of Cf2Cf desugaring the run on dex, the compileResult is the Cf desugaring result
// while the runnableCompiledResult is the dexed compiledResult used to run on dex.
private final TestCompileResult<?, ? extends SingleTestRunResult<?>> runnableCompiledResult;
@@ -41,7 +47,8 @@
LibraryDesugaringSpecification libraryDesugaringSpecification,
CompilationSpecification compilationSpecification,
D8TestCompileResult customLibCompile,
- L8TestCompileResult l8Compile)
+ L8TestCompileResult l8Compile,
+ List<ExternalArtProfile> l8ResidualArtProfiles)
throws CompilationFailedException, IOException {
this.test = test;
this.compileResult = compileResult;
@@ -50,6 +57,7 @@
this.compilationSpecification = compilationSpecification;
this.customLibCompile = customLibCompile;
this.l8Compile = l8Compile;
+ this.l8ResidualArtProfiles = l8ResidualArtProfiles;
this.runnableCompiledResult = computeRunnableCompiledResult();
}
@@ -66,6 +74,20 @@
return this;
}
+ public <E extends Throwable> DesugaredLibraryTestCompileResult<T> inspectL8ResidualArtProfile(
+ ThrowingConsumer<ArtProfileInspector, E> consumer) throws E, IOException, ExecutionException {
+ return inspectL8ResidualArtProfile(
+ (rewrittenArtProfile, inspector) -> consumer.accept(rewrittenArtProfile));
+ }
+
+ public <E extends Throwable> DesugaredLibraryTestCompileResult<T> inspectL8ResidualArtProfile(
+ ThrowingBiConsumer<ArtProfileInspector, CodeInspector, E> consumer) throws E, IOException {
+ assertEquals(1, l8ResidualArtProfiles.size());
+ consumer.accept(
+ new ArtProfileInspector(l8ResidualArtProfiles.iterator().next()), l8Inspector());
+ return this;
+ }
+
public <E extends Throwable> DesugaredLibraryTestCompileResult<T> inspect(
ThrowingConsumer<CodeInspector, E> consumer) throws IOException, E {
compileResult.inspect(consumer);
diff --git a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
index 6f68f53..d1f909d 100644
--- a/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
+++ b/src/test/java/com/android/tools/r8/dex/container/DexContainerFormatBasicTest.java
@@ -3,13 +3,17 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex.container;
+import static com.android.tools.r8.dex.Constants.CHECKSUM_OFFSET;
import static com.android.tools.r8.dex.Constants.DATA_OFF_OFFSET;
import static com.android.tools.r8.dex.Constants.DATA_SIZE_OFFSET;
+import static com.android.tools.r8.dex.Constants.FILE_SIZE_OFFSET;
import static com.android.tools.r8.dex.Constants.MAP_OFF_OFFSET;
+import static com.android.tools.r8.dex.Constants.SIGNATURE_OFFSET;
import static com.android.tools.r8.dex.Constants.STRING_IDS_OFF_OFFSET;
import static com.android.tools.r8.dex.Constants.STRING_IDS_SIZE_OFFSET;
import static com.android.tools.r8.dex.Constants.TYPE_STRING_ID_ITEM;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ByteDataView;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
@@ -31,8 +35,10 @@
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.file.Path;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
+import java.util.zip.Adler32;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,7 +80,7 @@
.setMinApi(AndroidApiLevel.L)
.compile()
.writeToZip();
- assertEquals(2, unzipContent(outputA).size());
+ validateDex(outputA, 2);
Path outputB =
testForD8(Backend.DEX)
@@ -82,7 +88,7 @@
.setMinApi(AndroidApiLevel.L)
.compile()
.writeToZip();
- assertEquals(2, unzipContent(outputB).size());
+ validateDex(outputB, 2);
Path outputMerged =
testForD8(Backend.DEX)
@@ -90,7 +96,7 @@
.setMinApi(AndroidApiLevel.L)
.compile()
.writeToZip();
- assertEquals(4, unzipContent(outputMerged).size());
+ validateDex(outputMerged, 4);
}
@Test
@@ -129,13 +135,21 @@
validateSingleContainerDex(outputB);
}
- private void validateSingleContainerDex(Path output) throws IOException {
+ private void validateDex(Path output, int expectedDexes) throws Exception {
List<byte[]> dexes = unzipContent(output);
- assertEquals(1, dexes.size());
- validateStringIdsSizeAndOffsets(dexes.get(0));
+ assertEquals(expectedDexes, dexes.size());
+ for (byte[] dex : dexes) {
+ validate(dex);
+ }
}
- private void validateStringIdsSizeAndOffsets(byte[] dex) {
+ private void validateSingleContainerDex(Path output) throws Exception {
+ List<byte[]> dexes = unzipContent(output);
+ assertEquals(1, dexes.size());
+ validate(dexes.get(0));
+ }
+
+ private void validate(byte[] dex) throws Exception {
CompatByteBuffer buffer = CompatByteBuffer.wrap(dex);
setByteOrder(buffer);
@@ -158,9 +172,53 @@
assertEquals(stringIdsOffset, buffer.getInt(sectionOffset + STRING_IDS_OFF_OFFSET));
assertEquals(stringIdsSize, getSizeFromMap(TYPE_STRING_ID_ITEM, buffer, sectionOffset));
assertEquals(stringIdsOffset, getOffsetFromMap(TYPE_STRING_ID_ITEM, buffer, sectionOffset));
+ validateMap(buffer, sectionOffset);
+ validateSignature(buffer, sectionOffset);
+ validateChecksum(buffer, sectionOffset);
}
}
+ private void validateMap(CompatByteBuffer buffer, int offset) {
+ int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
+ buffer.position(mapOffset);
+ int mapSize = buffer.getInt();
+ int previousOffset = Integer.MAX_VALUE;
+ for (int i = 0; i < mapSize; i++) {
+ buffer.getShort(); // Skip section type.
+ buffer.getShort(); // Skip unused.
+ buffer.getInt(); // Skip section size.
+ int o = buffer.getInt();
+ if (i > 0) {
+ assertTrue("" + i + ": " + o + " " + previousOffset, o > previousOffset);
+ }
+ previousOffset = o;
+ }
+ }
+
+ private void validateSignature(CompatByteBuffer buffer, int offset) throws Exception {
+ int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ md.update(
+ buffer.asByteBuffer().array(),
+ offset + FILE_SIZE_OFFSET,
+ sectionSize - offset - FILE_SIZE_OFFSET);
+ byte[] expectedSignature = new byte[20];
+ md.digest(expectedSignature, 0, 20);
+ for (int i = 0; i < expectedSignature.length; i++) {
+ assertEquals(expectedSignature[i], buffer.get(offset + SIGNATURE_OFFSET + i));
+ }
+ }
+
+ private void validateChecksum(CompatByteBuffer buffer, int offset) {
+ int sectionSize = buffer.getInt(offset + FILE_SIZE_OFFSET);
+ Adler32 adler = new Adler32();
+ adler.update(
+ buffer.asByteBuffer().array(),
+ offset + SIGNATURE_OFFSET,
+ sectionSize - offset - SIGNATURE_OFFSET);
+ assertEquals((int) adler.getValue(), buffer.getInt(offset + CHECKSUM_OFFSET));
+ }
+
private int getSizeFromMap(int type, CompatByteBuffer buffer, int offset) {
int mapOffset = buffer.getInt(offset + MAP_OFF_OFFSET);
buffer.position(mapOffset);
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
index e43398a..922a3ce 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepFooIfBarSameClassTest.java
@@ -84,8 +84,7 @@
assertThat(inspector.clazz(B.class), isPresent());
assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("foo"), isAbsent());
assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("bar"), isAbsent());
- // TODO(b/248408342): R8 full is keeping the default constructor. Avoid that.
- assertThat(inspector.clazz(B.class).uniqueInstanceInitializer(), isPresent());
+ assertThat(inspector.clazz(B.class).uniqueInstanceInitializer(), isAbsent());
}
@KeepEdge(consequences = {@KeepTarget(classConstant = A.class)})
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
new file mode 100644
index 0000000..f4060f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepSameMethodTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2023, 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 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.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+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.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepSameMethodTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("foo");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepSameMethodTest(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 testWithRuleExtraction() throws Exception {
+ List<String> rules = getExtractedKeepRules();
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getInputClassesWithoutAnnotations())
+ .addKeepRules(rules)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ // The "all members" target will create an unused "all fields" rule.
+ .allowUnusedProguardConfigurationRules()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class);
+ }
+
+ public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
+ }
+
+ public List<String> getExtractedKeepRules() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<String> rules = new ArrayList<>();
+ for (Class<?> clazz : classes) {
+ rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+ }
+ return rules;
+ }
+
+ private void checkOutput(CodeInspector inspector) throws Exception {
+ assertThat(inspector.clazz(A.class).method(A.class.getMethod("foo")), isPresent());
+ // TODO(b/265892343): The extracted rule will match all params so this is incorrectly kept.
+ assertThat(inspector.clazz(A.class).method(A.class.getMethod("foo", int.class)), isPresent());
+ // Bar is unused and thus removed.
+ assertThat(inspector.clazz(A.class).uniqueMethodWithOriginalName("bar"), isAbsent());
+ }
+
+ /**
+ * This conditional rule expresses that if any class in the program has a live member then that
+ * same member is to be kept (soft-pinned, e.g., no inlining, no renaming of the member, etc.).
+ */
+ @KeepEdge(
+ bindings = {
+ @KeepBinding(
+ bindingName = "AnyMemberOnA",
+ kind = KeepItemKind.ONLY_MEMBERS,
+ classConstant = A.class)
+ },
+ preconditions = {@KeepCondition(memberFromBinding = "AnyMemberOnA")},
+ consequences = {@KeepTarget(memberFromBinding = "AnyMemberOnA")})
+ static class A {
+
+ public void foo() throws Exception {
+ System.out.println(new Exception().getStackTrace()[0].getMethodName());
+ }
+
+ // TODO(b/265892343): There is no backref support for "any params", thus this method is hit by
+ // the extracted rule.
+ public void foo(int unused) throws Exception {
+ System.out.println(new Exception().getStackTrace()[0].getMethodName());
+ }
+
+ public void bar() throws Exception {
+ System.out.println(new Exception().getStackTrace()[0].getMethodName());
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
new file mode 100644
index 0000000..02e86ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepTargetClassAndMemberKindTest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2023, 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 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.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+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.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepTargetClassAndMemberKindTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepTargetClassAndMemberKindTest(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 testWithRuleExtraction() throws Exception {
+ List<String> rules = getExtractedKeepRules();
+ for (String rule : rules) {
+ System.out.println(rule);
+ }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getInputClassesWithoutAnnotations())
+ .addKeepRules(rules)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .allowUnusedProguardConfigurationRules()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class, B.class, C.class);
+ }
+
+ public List<byte[]> getInputClassesWithoutAnnotations() throws Exception {
+ return KeepEdgeAnnotationsTest.getInputClassesWithoutKeepAnnotations(getInputClasses());
+ }
+
+ public List<String> getExtractedKeepRules() throws Exception {
+ List<Class<?>> classes = getInputClasses();
+ List<String> rules = new ArrayList<>();
+ for (Class<?> clazz : classes) {
+ rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz));
+ }
+ return rules;
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ assertThat(inspector.clazz(C.class), isAbsent());
+ assertThat(inspector.clazz(B.class).method("void", "bar"), isPresent());
+ assertThat(inspector.clazz(B.class).method("void", "bar", "int"), isAbsent());
+ }
+
+ static class A {
+
+ @UsesReflection({
+ // Ensure that the class A remains as we are assuming the contents of its name.
+ @KeepTarget(classConstant = A.class),
+ // Ensure that the class B remains as we are looking it up by reflected name.
+ // Ensure the method 'bar' remains as we are invoking it by reflected name.
+ // Explicitly using kind CLASS_AND_MEMBER ensures that both the type and the method are kept.
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_MEMBERS,
+ classConstant = B.class,
+ methodName = "bar",
+ methodParameters = {},
+ methodReturnType = "void")
+ })
+ public void foo() throws Exception {
+ Class<?> clazz = Class.forName(A.class.getTypeName().replace("$A", "$B"));
+ clazz.getDeclaredMethod("bar").invoke(clazz);
+ }
+
+ // This annotation is not active as its implicit precondition "void A.foo(int)" is not used.
+ @UsesReflection({@KeepTarget(classConstant = C.class)})
+ public void foo(int unused) {
+ // Unused.
+ }
+ }
+
+ static class B {
+ public static void bar() {
+ System.out.println("Hello, world");
+ }
+
+ public static void bar(int ignore) {
+ throw new RuntimeException("UNUSED");
+ }
+ }
+
+ static class C {
+ // Unused.
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index b85a41b..dbe4642 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -47,8 +47,7 @@
.addTarget(KeepTarget.builder().setItemPattern(KeepItemPattern.any()).build())
.build())
.build();
- assertEquals(
- StringUtils.unixLines("-keep class *", "-keepclassmembers class * { *; }"), extract(edge));
+ assertEquals(StringUtils.unixLines("-keep class * { *; }"), extract(edge));
}
@Test
@@ -70,11 +69,7 @@
String allows = String.join(",allow", options);
// The "any" item will be split in two rules, one for the targeted types and one for the
// targeted members.
- assertEquals(
- StringUtils.unixLines(
- "-keep,allow" + allows + " class *",
- "-keepclassmembers,allow" + allows + " class * { *; }"),
- extract(edge));
+ assertEquals(StringUtils.unixLines("-keep,allow" + allows + " class * { *; }"), extract(edge));
}
@Test
@@ -93,9 +88,7 @@
.build();
// Allow is just the ordered list of options.
assertEquals(
- StringUtils.unixLines(
- "-keep,allowshrinking,allowobfuscation class *",
- "-keepclassmembers,allowshrinking,allowobfuscation class * { *; }"),
+ StringUtils.unixLines("-keep,allowshrinking,allowobfuscation class * { *; }"),
extract(edge));
}
@@ -104,7 +97,8 @@
KeepTarget target = target(classItem(CLASS));
KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build();
KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
- assertEquals(StringUtils.unixLines("-keep class " + CLASS), extract(edge));
+ assertEquals(
+ StringUtils.unixLines("-keep class " + CLASS + " { void finalize(); }"), extract(edge));
}
@Test
@@ -140,7 +134,9 @@
.setConsequences(KeepConsequences.builder().addTarget(target(classItem(CLASS))).build())
.build();
assertEquals(
- StringUtils.unixLines("-if class " + CLASS + " -keep class " + CLASS), extract(edge));
+ StringUtils.unixLines(
+ "-if class " + CLASS + " -keep class " + CLASS + " { void finalize(); }"),
+ extract(edge));
}
@Test
@@ -163,8 +159,7 @@
.build();
assertEquals(
StringUtils.unixLines(
- "-if class " + CLASS + " -keep class " + CLASS,
- "-keepclassmembers class " + CLASS + " { void <init>(); }"),
+ "-if class " + CLASS + " -keep class " + CLASS + " { void <init>(); }"),
extract(edge));
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
index 11ad201..4f334aa 100644
--- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -94,7 +94,7 @@
.compile()
.inspect(
inspector ->
- assertEqualMetadata(
+ assertEqualMetadataWithStringPoolValidation(
new CodeInspector(BASE_LIBRARY),
inspector,
(addedStrings, addedNonInitStrings) -> {}))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 55a048e..07539ed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.kotlin.KotlinMetadataAnnotationWrapper;
import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.IntBox;
@@ -31,8 +32,8 @@
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import junit.framework.TestCase;
-import kotlinx.metadata.jvm.KotlinClassHeader;
import kotlinx.metadata.jvm.KotlinClassMetadata;
+import org.junit.Assert;
public abstract class KotlinMetadataTestBase extends KotlinTestBase {
@@ -54,7 +55,7 @@
static final String KT_FUNCTION1 = "Lkotlin/Function1;";
static final String KT_COMPARABLE = "Lkotlin/Comparable;";
- public void assertEqualMetadata(
+ public void assertEqualMetadataWithStringPoolValidation(
CodeInspector originalInspector,
CodeInspector rewrittenInspector,
BiConsumer<Integer, Integer> addedStringsInspector) {
@@ -73,9 +74,11 @@
continue;
}
assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
+ KotlinMetadataAnnotationWrapper originalHeader =
+ KotlinMetadataAnnotationWrapper.wrap(originalMetadata);
+ KotlinMetadataAnnotationWrapper rewrittenHeader =
+ KotlinMetadataAnnotationWrapper.wrap(rewrittenMetadata);
+ TestCase.assertEquals(originalHeader.kind(), rewrittenHeader.kind());
// We cannot assert equality of the data since it may be ordered differently. However, we
// will check for the changes to the string pool and then validate the same parsing
@@ -87,8 +90,8 @@
.computeIfAbsent(
method.getFinalSignature().toDescriptor(), ignoreArgument(ArrayList::new))
.add(method.getFinalName()));
- HashSet<String> originalStrings = new HashSet<>(Arrays.asList(originalHeader.getData2()));
- HashSet<String> rewrittenStrings = new HashSet<>(Arrays.asList(rewrittenHeader.getData2()));
+ HashSet<String> originalStrings = new HashSet<>(Arrays.asList(originalHeader.data2()));
+ HashSet<String> rewrittenStrings = new HashSet<>(Arrays.asList(rewrittenHeader.data2()));
rewrittenStrings.forEach(
rewrittenString -> {
if (originalStrings.contains(rewrittenString)) {
@@ -108,7 +111,7 @@
}
addedNonInitStrings.increment();
});
- assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+ assertEquals(originalHeader.packageName(), rewrittenHeader.packageName());
String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
@@ -117,6 +120,57 @@
addedStringsInspector.accept(addedStrings.get(), addedNonInitStrings.get());
}
+ public void assertEqualDeserializedMetadata(
+ CodeInspector inspector, CodeInspector otherInspector) {
+ for (FoundClassSubject clazzSubject : otherInspector.allClasses()) {
+ ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
+ assertThat(r8Clazz, isPresent());
+ KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+ KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
+ if (originalMetadata == null) {
+ assertNull(rewrittenMetadata);
+ continue;
+ }
+ assertNotNull(rewrittenMetadata);
+ KotlinMetadataAnnotationWrapper originalHeader =
+ KotlinMetadataAnnotationWrapper.wrap(originalMetadata);
+ KotlinMetadataAnnotationWrapper rewrittenHeader =
+ KotlinMetadataAnnotationWrapper.wrap(rewrittenMetadata);
+ TestCase.assertEquals(originalHeader.kind(), rewrittenHeader.kind());
+ TestCase.assertEquals(originalHeader.packageName(), rewrittenHeader.packageName());
+ // We cannot assert equality of the data since it may be ordered differently. We use the
+ // KotlinMetadataWriter to deserialize the metadata and assert those are equal.
+ String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+ String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+ TestCase.assertEquals(expected, actual);
+ }
+ }
+
+ public void assertEqualMetadata(CodeInspector inspector, CodeInspector otherInspector) {
+ for (FoundClassSubject clazzSubject : otherInspector.allClasses()) {
+ ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
+ assertThat(r8Clazz, isPresent());
+ KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+ KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
+ if (originalMetadata == null) {
+ assertNull(rewrittenMetadata);
+ continue;
+ }
+ TestCase.assertNotNull(rewrittenMetadata);
+ KotlinMetadataAnnotationWrapper originalHeader =
+ KotlinMetadataAnnotationWrapper.wrap(originalMetadata);
+ KotlinMetadataAnnotationWrapper rewrittenHeader =
+ KotlinMetadataAnnotationWrapper.wrap(rewrittenMetadata);
+ TestCase.assertEquals(originalHeader.kind(), rewrittenHeader.kind());
+ TestCase.assertEquals(originalHeader.packageName(), rewrittenHeader.packageName());
+ Assert.assertArrayEquals(originalHeader.data1(), rewrittenHeader.data1());
+ Assert.assertArrayEquals(originalHeader.data2(), rewrittenHeader.data2());
+ String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+ String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+ TestCase.assertEquals(expected, actual);
+ }
+ }
+
public static void verifyExpectedWarningsFromKotlinReflectAndStdLib(
TestCompileResult<?, ?> compileResult) {
compileResult.assertAllWarningMessagesMatch(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
index 5e5fc28..bcbc386 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
@@ -4,26 +4,13 @@
package com.android.tools.r8.kotlin.metadata;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNotNull;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNull;
-
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -97,7 +84,13 @@
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
- .inspect(this::inspect)
+ // Since this has a keep-all classes rule, we should just assert that the meta-data is
+ // equal to the original one.
+ .inspect(
+ inspector ->
+ assertEqualDeserializedMetadata(
+ inspector,
+ new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion))))
.writeToZip();
Path main =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
@@ -112,33 +105,6 @@
.assertSuccessWithOutput(EXPECTED);
}
- private void inspect(CodeInspector inspector) throws IOException, ExecutionException {
- // Since this has a keep-all classes rule, we should just assert that the meta-data is equal to
- // the original one.
- CodeInspector stdLibInspector =
- new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion));
- for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
- ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
- assertThat(r8Clazz, isPresent());
- KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
- KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
- if (originalMetadata == null) {
- assertNull(rewrittenMetadata);
- continue;
- }
- assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
- assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
- // We cannot assert equality of the data since it may be ordered differently. Instead we use
- // the KotlinMetadataWriter.
- String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
- String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- assertEquals(expected, actual);
- }
- }
-
@Test
public void testMetadataForReflect() throws Exception {
Path libJar =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteContextReceiverTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteContextReceiverTest.java
new file mode 100644
index 0000000..102a056
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteContextReceiverTest.java
@@ -0,0 +1,193 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_8_0;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion.JAVA_8;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteContextReceiverTest extends KotlinMetadataTestBase {
+
+ private static final String PKG_LIB = PKG + ".context_receiver_lib";
+ private static final String PKG_APP = PKG + ".context_receiver_app";
+ private static final Path LIB_FILE =
+ getFileInTest(PKG_PREFIX + "/context_receiver_lib", "lib.txt");
+ private static final Path MAIN_FILE =
+ getFileInTest(PKG_PREFIX + "/context_receiver_app", "main.txt");
+ private static final String MAIN = PKG_APP + ".MainKt";
+ private final TestParameters parameters;
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "FooImpl::m1",
+ "BarImpl::m2",
+ "BazImpl::m3",
+ "BazImpl::m3",
+ "FooImpl::m1",
+ "BarImpl::m2",
+ "Hello World!");
+
+ @Parameterized.Parameters(name = "{0}, {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(),
+ getKotlinTestParameters()
+ .withCompilersStartingFromIncluding(KOTLINC_1_8_0)
+ .withOldCompilersStartingFrom(KOTLINC_1_8_0)
+ .withTargetVersion(JAVA_8)
+ .build());
+ }
+
+ public MetadataRewriteContextReceiverTest(
+ TestParameters parameters, KotlinTestParameters kotlinParameters) {
+ super(kotlinParameters);
+ this.parameters = parameters;
+ }
+
+ private static final KotlinCompileMemoizer libJars =
+ getCompileMemoizer()
+ .configure(
+ kotlinc ->
+ kotlinc
+ .addSourceFilesWithNonKtExtension(getStaticTemp(), LIB_FILE)
+ .enableExperimentalContextReceivers());
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+ .addSourceFilesWithNonKtExtension(temp, MAIN_FILE)
+ .setOutputPath(temp.newFolder().toPath())
+ .enableExperimentalContextReceivers()
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ kotlinc.getKotlinStdlibJar(),
+ kotlinc.getKotlinReflectJar(),
+ libJars.getForConfiguration(kotlinc, targetVersion))
+ .addClasspath(output)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForCompilationWithKeepAll() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(
+ libJars.getForConfiguration(kotlinc, targetVersion), kotlinc.getKotlinStdlibJar())
+ .addKeepClassAndMembersRules(PKG_LIB + ".*")
+ .addKeepAttributes(
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .addKeepKotlinMetadata()
+ .allowDiagnosticWarningMessages()
+ .addOptionsModification(
+ options -> options.testing.keepMetadataInR8IfNotRewritten = false)
+ .compile()
+ // Since this has a keep-all classes rule assert that the meta-data is equal to the
+ // original one.
+ .inspect(
+ inspector ->
+ assertEqualDeserializedMetadata(
+ inspector,
+ new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion))))
+ .writeToZip();
+ Path main =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFilesWithNonKtExtension(temp, MAIN_FILE)
+ .setOutputPath(temp.newFolder().toPath())
+ .enableExperimentalContextReceivers()
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), libJar)
+ .addClasspath(main)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataInExtensionFunction_renamedKotlinSources() throws Exception {
+ R8TestCompileResult r8LibraryResult =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+ // Ensure that we do not rename members
+ .addKeepRules("-keepclassmembers class * { *; }")
+ // Keep the Foo class but rename it.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".Foo")
+ // Keep the Bar class but rename it.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".Bar")
+ // Keep the Baz class but rename it.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".Baz")
+ // Keep all Printer fields.
+ .addKeepRules("-keep class " + PKG_LIB + ".Printer { *; }")
+ // Keep Super, but allow minification.
+ .addKeepRules("-keep class " + PKG_LIB + ".LibKt { <methods>; }")
+ .addKeepKotlinMetadata()
+ .compile();
+
+ // Rewrite the kotlin source to rewrite the classes from the mapping file
+ String kotlinSource = FileUtils.readTextFile(MAIN_FILE, StandardCharsets.UTF_8);
+
+ CodeInspector inspector = r8LibraryResult.inspector();
+ // Rewrite the source kotlin file that reference the renamed classes uses in the context
+ // receivers.
+ for (String className : new String[] {"Foo", "Bar", "Baz"}) {
+ String originalClassName = PKG_LIB + "." + className;
+ ClassSubject clazz = inspector.clazz(originalClassName);
+ assertThat(clazz, isPresentAndRenamed());
+ kotlinSource =
+ kotlinSource.replace("import " + originalClassName, "import " + clazz.getFinalName());
+ kotlinSource =
+ kotlinSource.replace(
+ ": " + className + " {",
+ ": "
+ + DescriptorUtils.getSimpleClassNameFromDescriptor(clazz.getFinalDescriptor())
+ + " {");
+ }
+
+ Path newSource = temp.newFolder().toPath().resolve("main.kt");
+ Files.write(newSource, kotlinSource.getBytes(StandardCharsets.UTF_8));
+
+ Path libJar = r8LibraryResult.writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(newSource)
+ .setOutputPath(temp.newFolder().toPath())
+ .enableExperimentalContextReceivers()
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlineClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlineClassTest.java
index af22ae1..0704ecd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlineClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlineClassTest.java
@@ -5,24 +5,15 @@
package com.android.tools.r8.kotlin.metadata;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_6_0;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
-import junit.framework.TestCase;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -84,7 +75,11 @@
"-keep class " + PKG + ".inline_class_lib.LibKt { *** login-*(java.lang.String); }")
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
- .inspect(this::inspect)
+ .inspect(
+ inspector ->
+ assertEqualMetadata(
+ inspector,
+ new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion))))
.writeToZip();
Path main =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
@@ -98,24 +93,4 @@
.run(parameters.getRuntime(), PKG + ".inline_class_app.MainKt")
.assertSuccessWithOutput(EXPECTED);
}
-
- private void inspect(CodeInspector inspector) throws IOException {
- CodeInspector stdLibInspector =
- new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion));
- ClassSubject clazzSubject = stdLibInspector.clazz(passwordTypeName);
- ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
- assertThat(r8Clazz, isPresent());
- KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
- KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
- TestCase.assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
- TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
- Assert.assertArrayEquals(originalHeader.getData1(), rewrittenHeader.getData1());
- Assert.assertArrayEquals(originalHeader.getData2(), rewrittenHeader.getData2());
- String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
- String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- TestCase.assertEquals(expected, actual);
- }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
index c693a41..6f78827 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
@@ -4,24 +4,13 @@
package com.android.tools.r8.kotlin.metadata;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNull;
-
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
-import junit.framework.TestCase;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -78,7 +67,11 @@
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
- .inspect(this::inspect)
+ .inspect(
+ inspector ->
+ assertEqualDeserializedMetadata(
+ inspector,
+ new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion))))
.writeToZip();
Path main =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
@@ -92,29 +85,4 @@
.run(parameters.getRuntime(), PKG + ".inline_property_app.MainKt")
.assertSuccessWithOutput(EXPECTED);
}
-
- private void inspect(CodeInspector inspector) throws IOException {
- CodeInspector stdLibInspector =
- new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion));
- for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
- ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
- assertThat(r8Clazz, isPresent());
- KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
- KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
- if (originalMetadata == null) {
- assertNull(rewrittenMetadata);
- continue;
- }
- TestCase.assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
- TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
- // We cannot assert equality of the data since it may be ordered differently. Instead we use
- // the KotlinMetadataWriter.
- String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
- String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- TestCase.assertEquals(expected, actual);
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
index 24b3e58..ce9fba4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -10,10 +10,12 @@
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.kotlin.KotlinMetadataAnnotationWrapper;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.util.Collection;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -68,8 +70,9 @@
if (clazz.getFinalName().startsWith("kotlin.io")
|| clazz.getFinalName().equals("kotlin.Metadata")
|| clazz.getFinalName().equals("kotlin.jvm.JvmName")) {
- assertNotNull(clazz.getKotlinClassMetadata());
- assertNotNull(clazz.getKotlinClassMetadata().getHeader().getData2());
+ KotlinClassMetadata kotlinClassMetadata = clazz.getKotlinClassMetadata();
+ assertNotNull(kotlinClassMetadata);
+ assertNotNull(KotlinMetadataAnnotationWrapper.wrap(kotlinClassMetadata).data2());
} else {
assertNull(clazz.getKotlinClassMetadata());
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteLocalDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteLocalDelegatedPropertyTest.java
index 2a7ee54..9fddbf0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteLocalDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteLocalDelegatedPropertyTest.java
@@ -70,7 +70,7 @@
.compile()
.inspect(
inspector ->
- assertEqualMetadata(
+ assertEqualMetadataWithStringPoolValidation(
new CodeInspector(jars.getForConfiguration(kotlinc, targetVersion)),
inspector,
(addedStrings, addedNonInitStrings) -> {}))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index ceb7d56..e06e1ba 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -50,14 +50,13 @@
.addKeepAllClassesRule()
.addKeepKotlinMetadata()
.addKeepAttributes(
- ProguardKeepAttributes.INNER_CLASSES,
- ProguardKeepAttributes.ENCLOSING_METHOD)
+ ProguardKeepAttributes.INNER_CLASSES, ProguardKeepAttributes.ENCLOSING_METHOD)
.allowDiagnosticWarningMessages()
.compile()
.assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
.inspect(
inspector ->
- assertEqualMetadata(
+ assertEqualMetadataWithStringPoolValidation(
new CodeInspector(kotlinc.getKotlinStdlibJar()),
inspector,
(addedStrings, addedNonInitStrings) -> {
@@ -75,7 +74,7 @@
.compile()
.inspect(
inspector ->
- assertEqualMetadata(
+ assertEqualMetadataWithStringPoolValidation(
new CodeInspector(kotlinc.getKotlinStdlibJar()),
inspector,
(addedStrings, addedNonInitStrings) -> {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java
index 7686ac3..6012fb0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRawTest.java
@@ -96,7 +96,7 @@
.compile()
.inspect(
inspector ->
- assertEqualMetadata(
+ assertEqualMetadataWithStringPoolValidation(
new CodeInspector(libJars.getForConfiguration(kotlinc, targetVersion)),
inspector,
(addedStrings, addedNonInitStrings) -> {}))
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java
index 5d13395..4683d0b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteUnitPrimitiveTest.java
@@ -6,27 +6,16 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MIN_SUPPORTED_VERSION;
import static com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion.JAVA_8;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNull;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
-import junit.framework.TestCase;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
-import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -109,7 +98,12 @@
.compile()
.assertAllWarningMessagesMatch(
equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
- .inspect(this::inspect)
+ .inspect(
+ inspector ->
+ assertEqualMetadata(
+ inspector,
+ new CodeInspector(
+ kotlincLibJar.getForConfiguration(kotlinc, targetVersion))))
.writeToZip();
Path main =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
@@ -124,29 +118,4 @@
.run(parameters.getRuntime(), PKG_APP + ".MainKt")
.assertSuccessWithOutput(EXPECTED);
}
-
- private void inspect(CodeInspector inspector) throws IOException {
- CodeInspector stdLibInspector =
- new CodeInspector(kotlincLibJar.getForConfiguration(kotlinc, targetVersion));
- for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
- ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
- assertThat(r8Clazz, isPresent());
- KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
- KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
- if (originalMetadata == null) {
- assertNull(rewrittenMetadata);
- continue;
- }
- TestCase.assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
- TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
- Assert.assertArrayEquals(originalHeader.getData1(), rewrittenHeader.getData1());
- Assert.assertArrayEquals(originalHeader.getData2(), rewrittenHeader.getData2());
- String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
- String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- TestCase.assertEquals(expected, actual);
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteValueClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteValueClassTest.java
index 6aecc85..b65643f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteValueClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteValueClassTest.java
@@ -9,7 +9,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.containsString;
-import static org.junit.Assert.assertNull;
import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestParameters;
@@ -20,13 +19,9 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
-import junit.framework.TestCase;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -112,31 +107,13 @@
}
private void inspect(CodeInspector inspector) throws IOException {
- CodeInspector stdLibInspector =
- new CodeInspector(kotlincLibJar.getForConfiguration(kotlinc, targetVersion));
- for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
- ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
- assertThat(r8Clazz, isPresent());
- KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
- KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
- if (originalMetadata == null) {
- assertNull(rewrittenMetadata);
- continue;
- }
- TestCase.assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
- TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
- // We cannot assert equality of the data since it may be ordered differently. Instead we use
- // the KotlinMetadataWriter.
- String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
- String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- TestCase.assertEquals(expected, actual);
- if (r8Clazz.getFinalName().equals(PKG_LIB + ".Name")) {
- assertThat(actual, containsString("inlineClassUnderlyingPropertyName"));
- assertThat(actual, containsString("inlineClassUnderlyingType"));
- }
- }
+ assertEqualDeserializedMetadata(
+ inspector, new CodeInspector(kotlincLibJar.getForConfiguration(kotlinc, targetVersion)));
+ ClassSubject r8Clazz = inspector.clazz(PKG_LIB + ".Name");
+ assertThat(r8Clazz, isPresent());
+ String actual =
+ KotlinMetadataWriter.kotlinMetadataToString("", r8Clazz.getKotlinClassMetadata());
+ assertThat(actual, containsString("inlineClassUnderlyingPropertyName"));
+ assertThat(actual, containsString("inlineClassUnderlyingType"));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_app/main.txt b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_app/main.txt
new file mode 100644
index 0000000..9380c6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_app/main.txt
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.context_receiver_app
+
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Bar
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Foo
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Baz
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Printer
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.callFooBar
+
+class FooImpl : Foo {
+
+ override fun m1(): String {
+ println("FooImpl::m1")
+ return "Hello "
+ }
+}
+
+class BarImpl : Bar {
+ override fun m2(): String {
+ println("BarImpl::m2")
+ return "World!"
+ }
+}
+
+class BazImpl : Baz {
+ override fun m3(): String {
+ println("BazImpl::m3")
+ return "BazImpl::m3"
+ }
+}
+
+fun main() {
+ with (FooImpl()) {
+ with (BarImpl()) {
+ val printer = Printer()
+ printer.fooBar()
+ with (BazImpl()) {
+ println(printer.getValue)
+ println(callFooBar())
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_lib/lib.txt b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_lib/lib.txt
new file mode 100644
index 0000000..0235848
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_lib/lib.txt
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.context_receiver_lib
+
+interface Foo {
+ fun m1(): String
+}
+
+interface Bar {
+ fun m2(): String
+}
+
+interface Baz {
+ fun m3(): String
+}
+
+context(Foo, Bar)
+class Printer {
+
+ fun fooBar() {
+ m1();
+ m2();
+ }
+
+ context(Baz)
+ val getValue: String
+ get() = if (System.currentTimeMillis() == 0L) "foo" else m3()
+}
+
+context(Foo, Bar)
+fun callFooBar() : String {
+ return m1() + m2();
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java
index a57ce5e..ea5c219 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodTest.java
@@ -31,26 +31,26 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.foo -> a:",
" void f1(int) -> f2",
" # { id: 'com.android.tools.r8.residualsignature', signature:'(Z)I' }");
private static final String mappingBar =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"a -> b:",
" int f2(boolean) -> f3");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.foo -> b:",
" void f1(int) -> f3",
" # {'id':'com.android.tools.r8.residualsignature','signature':'(Z)I'}");
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
index 8647c07..a6aded1 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeDifferentMethodWithLineNumberTest.java
@@ -31,19 +31,19 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.foo -> a:",
" 1:1:int f1(boolean) -> f2",
" 2:2:void f1(int) -> f2",
" # { id: 'com.android.tools.r8.residualsignature', signature:'(Z)V' }");
private static final String mappingBar =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"a -> b:",
" 8:8:void f2(boolean):2:2 -> f3");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.foo -> b:",
" 1:1:int f1(boolean) -> f2",
" 8:8:void f1(int) -> f3",
@@ -51,8 +51,8 @@
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingLineNumberTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingLineNumberTest.java
index f56d9af..1799999 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingLineNumberTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingLineNumberTest.java
@@ -31,34 +31,34 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.Class1 -> A:",
" 1:1:com.Class1 m(com.Class2[][]):42:42 -> a",
"com.Class2 -> B:");
private static final String mappingBar =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"A -> B:",
" 2:2:A a(B[][]):1:1 -> b",
"B -> C:");
private static final String mappingBaz =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"B -> C:",
" 3:3:B b(C[][]):2:2 -> c",
"C -> D:");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.Class1 -> C:",
" 3:3:com.Class1 m(com.Class2[][]):42:42 -> c",
"com.Class2 -> D:");
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
- ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromStringWithExperimental(mappingBaz);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+ ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromStringWithPreamble(mappingBaz);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar, mappingForBaz);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingTest.java
index 17794b3..4b7e9a9 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeNoResidualSignatureExistingNamingTest.java
@@ -31,28 +31,28 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.Class1 -> A:",
" com.Class1 f -> a",
" com.Class1 m(com.Class2[][]) -> a",
"com.Class2 -> B:");
private static final String mappingBar =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"A -> B:",
" A a -> b",
" A a(B[][]) -> b",
"B -> C:");
private static final String mappingBaz =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"B -> C:",
" B b -> c",
" B b(C[][]) -> c",
"C -> D:");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.Class1 -> C:",
" com.Class1 f -> c",
" com.Class1 m(com.Class2[][]) -> c",
@@ -60,9 +60,9 @@
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
- ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromStringWithExperimental(mappingBaz);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+ ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromStringWithPreamble(mappingBaz);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar, mappingForBaz);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
index 587dc6a..657b551 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeOutlineTest.java
@@ -31,7 +31,7 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
"outline.Class -> a:",
" 1:2:int some.inlinee():75:76 -> a",
" 1:2:int outline():0 -> a",
@@ -44,11 +44,10 @@
" # { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 },"
+ " 'outline':'La;a()I' }");
private static final String mappingBar =
- StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}", "a -> b:");
+ StringUtils.unixLines("# {'id':'com.android.tools.r8.mapping','version':'2.2'}", "a -> b:");
private static final String mappingBaz =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"b -> c:",
" 4:5:int a():1:2 -> m",
"x -> y:",
@@ -56,7 +55,7 @@
" 42:42:int s(int):27:27 -> o");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"outline.Callsite -> y:",
" 8:8:int outlineCaller(int):23 -> o",
" 9:9:int foo.bar.baz.outlineCaller(int):98:98 -> o",
@@ -71,9 +70,9 @@
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
- ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromStringWithExperimental(mappingBaz);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+ ClassNameMapper mappingForBaz = ClassNameMapper.mapperFromStringWithPreamble(mappingBaz);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar, mappingForBaz);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
index 7b81cdc..93a9153 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeRewriteFrameTest.java
@@ -31,7 +31,7 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
"my.CustomException -> a:",
"foo.Bar -> x:",
" 4:4:void other.Class.inlinee():23:23 -> a",
@@ -40,13 +40,13 @@
+ "conditions: ['throws(La;)'], actions: ['removeInnerFrames(1)'] }");
private static final String mappingBar =
StringUtils.unixLines(
- "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
"a -> b:",
"x -> c:",
" 8:8:void a(Other.Class):4:4 -> m");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"foo.Bar -> c:",
" 8:8:void other.Class.inlinee():23:23 -> m",
" 8:8:void caller(Other.Class):7 -> m",
@@ -56,8 +56,8 @@
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
index 53180d3..0954d3a 100644
--- a/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeSyntheticTest.java
@@ -31,7 +31,7 @@
private static final String mappingFoo =
StringUtils.unixLines(
- "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
"com.foo -> a:",
"# { id: 'com.android.tools.r8.synthesized' }",
" int f -> a",
@@ -40,7 +40,7 @@
" # { id: 'com.android.tools.r8.synthesized' }");
private static final String mappingBar =
StringUtils.unixLines(
- "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
"a -> b:",
" int a -> b",
"com.bar -> c:",
@@ -49,7 +49,7 @@
" # { id: 'com.android.tools.r8.synthesized' }");
private static final String mappingResult =
StringUtils.unixLines(
- "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
"com.bar -> c:",
"# {'id':'com.android.tools.r8.synthesized'}",
" void bar() -> a",
@@ -57,14 +57,13 @@
"com.foo -> b:",
"# {'id':'com.android.tools.r8.synthesized'}",
" int f -> b",
- // TODO(b/242673239): When fixed, we should emit synthetized info here as well.
" void m() -> b",
" # {'id':'com.android.tools.r8.synthesized'}");
@Test
public void testCompose() throws Exception {
- ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithExperimental(mappingFoo);
- ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithExperimental(mappingBar);
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
assertEquals(mappingResult, doubleToSingleQuote(composed));
}
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeUnknownJsonTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeUnknownJsonTest.java
new file mode 100644
index 0000000..022f42f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeUnknownJsonTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, 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.mappingcompose;
+
+import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote;
+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.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MappingComposer;
+import com.android.tools.r8.utils.StringUtils;
+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 ComposeUnknownJsonTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static final String mappingFoo =
+ StringUtils.unixLines(
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
+ "# { id: 'some.unknown.identifier', settings: 'message1' }",
+ "com.foo -> a:",
+ "# { id: 'some.other.unknown.identifier', foo: 'message2' }");
+ private static final String mappingBar =
+ StringUtils.unixLines(
+ "# { id: 'com.android.tools.r8.mapping', version: '2.2' }",
+ "# { id: 'some.third.unknown.identifier', bar: 'message3' }",
+ "a -> b:",
+ "# { id: 'some.fourth.unknown.identifier', baz: 'message4' }");
+ private static final String mappingResult =
+ StringUtils.unixLines(
+ "# { id: 'some.unknown.identifier', settings: 'message1' }",
+ "# { id: 'some.third.unknown.identifier', bar: 'message3' }",
+ "# {'id':'com.android.tools.r8.mapping','version':'2.2'}",
+ "com.foo -> b:",
+ "# {'id':'some.other.unknown.identifier','foo':'message2'}",
+ "# {'id':'some.fourth.unknown.identifier','baz':'message4'}");
+
+ @Test
+ public void testCompose() throws Exception {
+ ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo);
+ ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar);
+ String composed = MappingComposer.compose(mappingForFoo, mappingForBar);
+ assertEquals(mappingResult, doubleToSingleQuote(composed));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java
index 922cae7..1d08f27 100644
--- a/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/ArtProfileCollisionAfterClassMergingRewritingTest.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -15,12 +14,11 @@
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.model.ExternalArtProfileClassRule;
import com.android.tools.r8.profile.art.model.ExternalArtProfileMethodRule;
-import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
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.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.MethodReferenceUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -56,21 +54,17 @@
@Test
public void test() throws Exception {
- Box<ExternalArtProfile> residualArtProfile = new Box<>();
testForR8(Backend.DEX)
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
.addHorizontallyMergedClassesInspector(
inspector ->
inspector.assertMergedInto(Foo.class, Bar.class).assertNoOtherClassesMerged())
- .apply(
- testBuilder ->
- ArtProfileTestingUtils.addArtProfileForRewriting(
- getArtProfile(), residualArtProfile::set, testBuilder))
+ .addArtProfileForRewriting(getArtProfile())
.enableInliningAnnotations()
.setMinApi(AndroidApiLevel.LATEST)
.compile()
- .inspect(inspector -> inspect(inspector, residualArtProfile.get()));
+ .inspectResidualArtProfile(this::inspect);
}
public ExternalArtProfile getArtProfile() {
@@ -85,7 +79,7 @@
.build();
}
- public ExternalArtProfile getExpectedResidualArtProfile(CodeInspector inspector) {
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
ClassSubject barClassSubject = inspector.clazz(Bar.class);
assertThat(barClassSubject, isPresentAndRenamed());
@@ -95,24 +89,13 @@
MethodSubject worldMethodSubject = barClassSubject.uniqueMethodWithOriginalName("world");
assertThat(worldMethodSubject, isPresentAndRenamed());
- return ExternalArtProfile.builder()
- .addRules(
- ExternalArtProfileClassRule.builder().setClassReference(mainClassReference).build(),
- ExternalArtProfileMethodRule.builder().setMethodReference(mainMethodReference).build(),
- ExternalArtProfileClassRule.builder()
- .setClassReference(barClassSubject.getFinalReference())
- .build(),
- ExternalArtProfileMethodRule.builder()
- .setMethodReference(helloMethodSubject.getFinalReference())
- .build(),
- ExternalArtProfileMethodRule.builder()
- .setMethodReference(worldMethodSubject.getFinalReference())
- .build())
- .build();
- }
-
- private void inspect(CodeInspector inspector, ExternalArtProfile residualArtProfile) {
- assertEquals(getExpectedResidualArtProfile(inspector), residualArtProfile);
+ profileInspector
+ .assertContainsClassRules(mainClassReference, barClassSubject.getFinalReference())
+ .assertContainsMethodRules(
+ mainMethodReference,
+ helloMethodSubject.getFinalReference(),
+ worldMethodSubject.getFinalReference())
+ .assertContainsNoOtherRules();
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java
index c5fb462..bbef17f 100644
--- a/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/ArtProfileRewritingTest.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -15,12 +14,11 @@
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.model.ExternalArtProfileClassRule;
import com.android.tools.r8.profile.art.model.ExternalArtProfileMethodRule;
-import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
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.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.MethodReferenceUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -53,18 +51,14 @@
@Test
public void test() throws Exception {
- Box<ExternalArtProfile> residualArtProfile = new Box<>();
testForR8(Backend.DEX)
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .apply(
- testBuilder ->
- ArtProfileTestingUtils.addArtProfileForRewriting(
- getArtProfile(), residualArtProfile::set, testBuilder))
+ .addArtProfileForRewriting(getArtProfile())
.enableInliningAnnotations()
.setMinApi(AndroidApiLevel.LATEST)
.compile()
- .inspect(inspector -> inspect(inspector, residualArtProfile.get()));
+ .inspectResidualArtProfile(this::inspect);
}
private ExternalArtProfile getArtProfile() {
@@ -84,33 +78,22 @@
.build();
}
- private ExternalArtProfile getExpectedResidualArtProfile(CodeInspector inspector) {
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
ClassSubject greeterClassSubject = inspector.clazz(Greeter.class);
assertThat(greeterClassSubject, isPresentAndRenamed());
MethodSubject greetMethodSubject = greeterClassSubject.uniqueMethodWithOriginalName("greet");
assertThat(greetMethodSubject, isPresentAndRenamed());
- return ExternalArtProfile.builder()
- .addRules(
- ExternalArtProfileClassRule.builder().setClassReference(mainClassReference).build(),
- ExternalArtProfileMethodRule.builder()
- .setMethodReference(mainMethodReference)
- .setMethodRuleInfo(ArtProfileMethodRuleInfoImpl.builder().setIsStartup().build())
- .build(),
- ExternalArtProfileClassRule.builder()
- .setClassReference(greeterClassSubject.getFinalReference())
- .build(),
- ExternalArtProfileMethodRule.builder()
- .setMethodReference(greetMethodSubject.getFinalReference())
- .setMethodRuleInfo(
- ArtProfileMethodRuleInfoImpl.builder().setIsHot().setIsPostStartup().build())
- .build())
- .build();
- }
-
- private void inspect(CodeInspector inspector, ExternalArtProfile residualArtProfile) {
- assertEquals(getExpectedResidualArtProfile(inspector), residualArtProfile);
+ profileInspector
+ .assertContainsClassRules(mainClassReference, greeterClassSubject.getFinalReference())
+ .inspectMethodRule(
+ mainMethodReference,
+ ruleInspector -> ruleInspector.assertIsStartup().assertNotHot().assertNotPostStartup())
+ .inspectMethodRule(
+ greetMethodSubject.getFinalReference(),
+ ruleInspector -> ruleInspector.assertIsHot().assertIsPostStartup().assertNotStartup())
+ .assertContainsNoOtherRules();
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java
index 52e0beb..3cd8e10 100644
--- a/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/DesugaredLibraryArtProfileRewritingTest.java
@@ -18,10 +18,9 @@
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.model.ExternalArtProfileMethodRule;
-import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -59,16 +58,12 @@
@Test
public void test() throws Throwable {
Assume.assumeTrue(libraryDesugaringSpecification.hasEmulatedInterfaceDesugaring(parameters));
- Box<ExternalArtProfile> residualArtProfile = new Box<>();
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .apply(
- testBuilder ->
- ArtProfileTestingUtils.addArtProfileForRewriting(
- getArtProfile(), residualArtProfile::set, testBuilder))
+ .addL8ArtProfileForRewriting(getArtProfile())
.compile()
- .inspectL8(inspector -> inspect(inspector, residualArtProfile.get()))
+ .inspectL8ResidualArtProfile(this::inspect)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("0");
}
@@ -91,16 +86,7 @@
.build();
}
- private ExternalArtProfile getExpectedResidualArtProfile(MethodSubject forEachMethodSubject) {
- return ExternalArtProfile.builder()
- .addRule(
- ExternalArtProfileMethodRule.builder()
- .setMethodReference(forEachMethodSubject.getFinalReference())
- .build())
- .build();
- }
-
- private void inspect(CodeInspector inspector, ExternalArtProfile residualArtProfile) {
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
ClassSubject consumerClassSubject =
inspector.clazz(
libraryDesugaringSpecification.functionPrefix(parameters) + ".util.function.Consumer");
@@ -117,7 +103,7 @@
&& libraryDesugaringSpecification == LibraryDesugaringSpecification.JDK8));
assertEquals(consumerClassSubject.asTypeSubject(), forEachMethodSubject.getParameter(0));
- assertEquals(getExpectedResidualArtProfile(forEachMethodSubject), residualArtProfile);
+ profileInspector.assertContainsMethodRule(forEachMethodSubject).assertContainsNoOtherRules();
}
static class Main {
diff --git a/src/test/java/com/android/tools/r8/profile/art/NoSuchClassAndMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/NoSuchClassAndMethodProfileRewritingTest.java
new file mode 100644
index 0000000..7b598a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/NoSuchClassAndMethodProfileRewritingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2023, 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.profile.art;
+
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+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 NoSuchClassAndMethodProfileRewritingTest 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)
+ .addArtProfileForRewriting(getArtProfile())
+ .setMinApi(parameters.getApiLevel())
+ // TODO(b/266178791): Emit a warning for each discarded item.
+ .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages)
+ .inspectResidualArtProfile(this::inspectResidualArtProfile)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() {
+ ClassReference missingClassReference = Reference.classFromDescriptor("Lfoo/Missing;");
+ return ExternalArtProfile.builder()
+ .addClassRule(missingClassReference)
+ .addMethodRule(Reference.methodFromDescriptor(missingClassReference, "m", "()V"))
+ .build();
+ }
+
+ private void inspectResidualArtProfile(ArtProfileInspector profileInspector) {
+ // None of the items in the profile exist in the input.
+ // TODO(b/266178791): Discard items from profile that is not in the input app.
+ profileInspector.assertNotEmpty();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/NonEmptyToEmptyProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/NonEmptyToEmptyProfileRewritingTest.java
new file mode 100644
index 0000000..60e14df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/NonEmptyToEmptyProfileRewritingTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2023, 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.profile.art;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 NonEmptyToEmptyProfileRewritingTest 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)
+ .addArtProfileForRewriting(getArtProfile())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .inspectResidualArtProfile(this::inspectResidualArtProfile)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() throws Exception {
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(Main.class.getDeclaredMethod("dead")))
+ .build();
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Verify Main.dead() is removed.
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ assertThat(mainClassSubject.uniqueMethodWithOriginalName("dead"), isAbsent());
+ }
+
+ private void inspectResidualArtProfile(ArtProfileInspector profileInspector) {
+ // After shaking of Main.dead() the ART profile should become empty.
+ profileInspector.assertEmpty();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+
+ static void dead() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
new file mode 100644
index 0000000..f5959e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/ApiOutlineProfileRewritingTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 ApiOutlineProfileRewritingTest extends TestBase {
+
+ private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public AndroidApiLevel getApiLevelForRuntime() {
+ return parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getRuntime().maxSupportedApiLevel();
+ }
+
+ public boolean isLibraryClassAlwaysPresent() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel);
+ }
+
+ public boolean isLibraryClassPresentInCurrentRuntime() {
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.getApiLevel() == AndroidApiLevel.B);
+ assertFalse(isLibraryClassPresentInCurrentRuntime());
+ testForJvm()
+ .addProgramClasses(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::inspectRunResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addLibraryClasses(LibraryClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .applyIf(
+ isLibraryClassPresentInCurrentRuntime(),
+ testBuilder -> testBuilder.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::inspectRunResult);
+ }
+
+ private ExternalArtProfile getArtProfile() {
+ return ExternalArtProfile.builder()
+ .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector)
+ throws Exception {
+ // Verify that outlining happened.
+ verifyThat(inspector, parameters, LibraryClass.class)
+ .hasConstClassOutlinedFromUntil(
+ Main.class.getMethod("main", String[].class), classApiLevel);
+
+ // Check outline was added to program.
+ ClassSubject apiOutlineClassSubject =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(Main.class, 0));
+ assertThat(apiOutlineClassSubject, notIf(isPresent(), isLibraryClassAlwaysPresent()));
+
+ MethodSubject apiOutlineMethodSubject = apiOutlineClassSubject.uniqueMethod();
+ assertThat(apiOutlineMethodSubject, notIf(isPresent(), isLibraryClassAlwaysPresent()));
+
+ // TODO(b/265729283): When outlining the residual profile should include the outline method and
+ // its holder.
+ profileInspector
+ .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .assertContainsNoOtherRules();
+ }
+
+ private void inspectRunResult(SingleTestRunResult<?> runResult) {
+ runResult.applyIf(
+ isLibraryClassPresentInCurrentRuntime(),
+ ignore -> runResult.assertSuccessWithOutputLines("class " + typeName(LibraryClass.class)),
+ ignore -> runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(LibraryClass.class);
+ }
+ }
+
+ // Only present from api 23.
+ public static class LibraryClass {}
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java
new file mode 100644
index 0000000..ba5013c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/BackportProfileRewritingTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+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 BackportProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .addOptionsModification(InlinerOptions::disableInlining)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("true");
+ }
+
+ private ExternalArtProfile getArtProfile() {
+ return ExternalArtProfile.builder()
+ .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .build();
+ }
+
+ private boolean isBackportingObjectsNonNull() {
+ return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ ClassSubject backportClassSubject =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticBackportClass(Main.class, 0));
+ assertThat(backportClassSubject, onlyIf(isBackportingObjectsNonNull(), isPresent()));
+
+ profileInspector.assertContainsMethodRules(MethodReferenceUtils.mainMethod(Main.class));
+
+ // TODO(b/265729283): Should include the backported method and its holder.
+ if (isBackportingObjectsNonNull()) {
+ MethodSubject backportMethodSubject = backportClassSubject.uniqueMethod();
+ assertThat(backportMethodSubject, isPresent());
+ }
+
+ profileInspector.assertContainsNoOtherRules();
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(Objects.nonNull(args));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java
new file mode 100644
index 0000000..4e59b75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/DefaultInterfaceMethodProfileRewritingTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 DefaultInterfaceMethodProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .addOptionsModification(InlinerOptions::disableInlining)
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() throws Exception {
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(I.class.getDeclaredMethod("m")))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+
+ MethodSubject interfaceMethodSubject = iClassSubject.uniqueMethodWithOriginalName("m");
+ assertThat(interfaceMethodSubject, isPresent());
+
+ profileInspector.assertContainsMethodRule(interfaceMethodSubject);
+ } else {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ ClassSubject companionClassSubject =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(I.class));
+ assertThat(companionClassSubject, isPresent());
+
+ MethodSubject interfaceMethodSubject = iClassSubject.uniqueMethodWithOriginalName("m");
+ assertThat(interfaceMethodSubject, isPresent());
+
+ MethodSubject implementationMethodSubject =
+ aClassSubject.method(interfaceMethodSubject.getFinalReference());
+ assertThat(implementationMethodSubject, isPresent());
+
+ MethodSubject movedMethodSubject = companionClassSubject.uniqueMethod();
+ assertThat(movedMethodSubject, isPresent());
+
+ // TODO(b/265729283): Should also include the two methods from desugaring.
+ profileInspector.assertContainsMethodRule(interfaceMethodSubject);
+ }
+
+ profileInspector.assertContainsNoOtherRules();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = System.currentTimeMillis() > 0 ? new A() : new B();
+ i.m();
+ }
+ }
+
+ interface I {
+
+ default void m() {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class A implements I {}
+
+ @NoHorizontalClassMerging
+ static class B implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
new file mode 100644
index 0000000..13a8c9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/EnumUnboxingUtilityMethodProfileRewritingTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 EnumUnboxingUtilityMethodProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(MyEnum.class))
+ .addOptionsModification(InlinerOptions::disableInlining)
+ .noHorizontalClassMergingOfSynthetics()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() throws Exception {
+ return ExternalArtProfile.builder()
+ .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .addMethodRule(Reference.methodFromMethod(MyEnum.class.getDeclaredMethod("greet")))
+ .addMethodRule(Reference.methodFromMethod(MyEnum.class.getDeclaredMethod("other")))
+ .addMethodRule(Reference.methodFromMethod(MyEnum.class.getDeclaredMethod("values")))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+ assertThat(mainClassSubject.mainMethod(), isPresent());
+
+ ClassSubject enumUnboxingLocalUtilityClassSubject =
+ inspector.clazz(
+ SyntheticItemsTestUtils.syntheticEnumUnboxingLocalUtilityClass(MyEnum.class));
+ assertThat(enumUnboxingLocalUtilityClassSubject, isPresent());
+
+ MethodSubject localGreetMethodSubject =
+ enumUnboxingLocalUtilityClassSubject.uniqueMethodWithOriginalName("greet");
+ assertThat(localGreetMethodSubject, isPresent());
+
+ MethodSubject localOtherMethodSubject =
+ enumUnboxingLocalUtilityClassSubject.uniqueMethodWithOriginalName("other");
+ assertThat(localOtherMethodSubject, isPresent());
+
+ MethodSubject localValuesMethodSubject =
+ enumUnboxingLocalUtilityClassSubject.uniqueMethodWithOriginalName("values");
+ assertThat(localValuesMethodSubject, isPresent());
+
+ ClassSubject enumUnboxingSharedUtilityClassSubject =
+ inspector.clazz(
+ SyntheticItemsTestUtils.syntheticEnumUnboxingSharedUtilityClass(MyEnum.class));
+ assertThat(enumUnboxingSharedUtilityClassSubject, isPresent());
+ assertThat(enumUnboxingSharedUtilityClassSubject.clinit(), isPresent());
+ assertThat(
+ enumUnboxingSharedUtilityClassSubject.uniqueMethodWithOriginalName("ordinal"), isPresent());
+ assertThat(
+ enumUnboxingSharedUtilityClassSubject.uniqueMethodWithOriginalName("values"), isPresent());
+
+ // TODO(b/265729283): Should also include the above methods from enum unboxing.
+ profileInspector
+ .assertContainsMethodRules(
+ mainClassSubject.mainMethod(),
+ localGreetMethodSubject,
+ localOtherMethodSubject,
+ localValuesMethodSubject)
+ .assertContainsNoOtherRules();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ MyEnum a = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+ MyEnum b = a.other();
+ a.greet();
+ b.greet();
+ }
+ }
+
+ enum MyEnum {
+ A,
+ B;
+
+ void greet() {
+ switch (this) {
+ case A:
+ System.out.print("Hello");
+ break;
+ case B:
+ System.out.println(", world!");
+ break;
+ }
+ }
+
+ MyEnum other() {
+ return MyEnum.values()[1 - ordinal()];
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java
new file mode 100644
index 0000000..985b723
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedConstructorMethodProfileRewritingTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HorizontallyMergedConstructorMethodProfileRewritingTest extends TestBase {
+
+ private enum ArtProfileInputOutput {
+ A_CONSTRUCTOR,
+ B_CONSTRUCTOR;
+
+ public ExternalArtProfile getArtProfile() throws Exception {
+ switch (this) {
+ case A_CONSTRUCTOR:
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(A.class.getDeclaredConstructor()))
+ .build();
+ case B_CONSTRUCTOR:
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(B.class.getDeclaredConstructor()))
+ .build();
+ default:
+ throw new RuntimeException();
+ }
+ }
+
+ public void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ MethodSubject syntheticConstructorSubject = aClassSubject.uniqueMethod();
+ assertThat(syntheticConstructorSubject, isPresent());
+
+ // TODO(b/265729283): Should contain the constructor.
+ profileInspector.assertEmpty();
+ }
+ }
+
+ @Parameter(0)
+ public ArtProfileInputOutput artProfileInputOutput;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ArtProfileInputOutput.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(artProfileInputOutput.getArtProfile())
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertMergedInto(B.class, A.class).assertNoOtherClassesMerged())
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(artProfileInputOutput::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A();
+ new B();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ public A() {
+ System.out.print("Hello");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ public B() {
+ System.out.println(", world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java
new file mode 100644
index 0000000..29e39f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/HorizontallyMergedVirtualMethodProfileRewritingTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HorizontallyMergedVirtualMethodProfileRewritingTest extends TestBase {
+
+ private enum ArtProfileInputOutput {
+ A_METHOD,
+ B_METHOD;
+
+ public ExternalArtProfile getArtProfile() throws Exception {
+ switch (this) {
+ case A_METHOD:
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(A.class.getDeclaredMethod("m")))
+ .build();
+ case B_METHOD:
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(B.class.getDeclaredMethod("m")))
+ .build();
+ default:
+ throw new RuntimeException();
+ }
+ }
+
+ public void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+
+ String stringConstInMovedMethod = this == A_METHOD ? "Hello" : ", world!";
+ MethodSubject movedMethodSubject =
+ aClassSubject.uniqueMethodThatMatches(
+ method ->
+ method.isPrivate()
+ && method
+ .streamInstructions()
+ .anyMatch(
+ instruction -> instruction.isConstString(stringConstInMovedMethod)));
+ assertThat(movedMethodSubject, isPresent());
+
+ MethodSubject syntheticBridgeMethodSubject =
+ aClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+ assertThat(syntheticBridgeMethodSubject, isPresent());
+
+ // TODO(b/265729283): Should contain the synthetic bridge method from above.
+ profileInspector.assertContainsMethodRule(movedMethodSubject).assertContainsNoOtherRules();
+ }
+ }
+
+ @Parameter(0)
+ public ArtProfileInputOutput artProfileInputOutput;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ArtProfileInputOutput.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(artProfileInputOutput.getArtProfile())
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertMergedInto(B.class, A.class).assertNoOtherClassesMerged())
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
+ .addOptionsModification(
+ options -> options.callSiteOptimizationOptions().setEnableMethodStaticizing(false))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(artProfileInputOutput::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new A().m();
+ new B().m();
+ }
+ }
+
+ static class A {
+
+ public void m() {
+ System.out.print("Hello");
+ }
+ }
+
+ static class B {
+
+ public void m() {
+ System.out.println(", world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java
new file mode 100644
index 0000000..75b9758
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/InvokeSpecialToVirtualMethodProfileRewritingTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+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.assertNotEquals;
+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.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+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.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToVirtualMethodProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClassFileData(getTransformedMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!", "Hello, world!");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedMain())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .addOptionsModification(InlinerOptions::disableInlining)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!", "Hello, world!");
+ }
+
+ private byte[] getTransformedMain() throws IOException {
+ BooleanBox anyRewritten = new BooleanBox();
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "main",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (opcode == Opcodes.INVOKEVIRTUAL) {
+ assertEquals("m", name);
+ if (anyRewritten.isFalse()) {
+ visitor.visitMethodInsn(
+ Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface);
+ anyRewritten.set();
+ return;
+ }
+ }
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ })
+ .transform();
+ }
+
+ private ExternalArtProfile getArtProfile() throws Exception {
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(Main.class.getDeclaredMethod("m")))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector)
+ throws Exception {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mMethodSubject = mainClassSubject.uniqueMethodWithOriginalName("m");
+ assertThat(mMethodSubject, isPresent());
+
+ if (parameters.isDexRuntime()) {
+ MethodSubject mMovedMethodSubject =
+ mainClassSubject.method(
+ SyntheticItemsTestUtils.syntheticInvokeSpecialMethod(
+ Main.class.getDeclaredMethod("m")));
+ assertThat(mMovedMethodSubject, isPresent());
+ assertNotEquals(
+ mMethodSubject.getProgramMethod().getName(),
+ mMovedMethodSubject.getProgramMethod().getName());
+ }
+
+ // TODO(b/265729283): Should also contain the synthetic method above when compiling to dex.
+ profileInspector.assertContainsMethodRule(mMethodSubject).assertContainsNoOtherRules();
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Main main = new Main();
+ main.m(); // transformed to invoke-special
+ main.m(); // remains an invoke-virtual (so that Main.m() survives shaking)
+ }
+
+ public void m() {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java
new file mode 100644
index 0000000..e2f0c2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/MovedStaticInterfaceMethodProfileRewritingTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 MovedStaticInterfaceMethodProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() throws Exception {
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(I.class.getDeclaredMethod("m")))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ ClassSubject iClassSubject = inspector.clazz(I.class);
+ assertThat(iClassSubject, isPresent());
+
+ MethodSubject mMethodSubject = iClassSubject.uniqueMethodWithOriginalName("m");
+ assertThat(mMethodSubject, isPresent());
+
+ profileInspector.assertContainsMethodRule(mMethodSubject).assertContainsNoOtherRules();
+ } else {
+ ClassSubject companionClassSubject =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(I.class));
+ assertThat(companionClassSubject, isPresent());
+
+ MethodSubject mMethodSubject = companionClassSubject.uniqueMethodWithOriginalName("m");
+ assertThat(mMethodSubject, isPresent());
+
+ // TODO(b/265729283): Should contain the companion method.
+ profileInspector.assertEmpty();
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I.m();
+ }
+ }
+
+ interface I {
+
+ @NeverInline
+ static void m() {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java
new file mode 100644
index 0000000..e376dbc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/NestBasedAccessBridgesProfileRewritingTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.profile.art.completeness.NestBasedAccessBridgesProfileRewritingTest.Main.NestMember;
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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;
+
+@RunWith(Parameterized.class)
+public class NestBasedAccessBridgesProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11));
+ testForJvm()
+ .addProgramClassFileData(getProgramClassFileData())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "3", "4");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeFalse(parameters.isCfRuntime() && parameters.asCfRuntime().isOlderThan(CfVm.JDK11));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getProgramClassFileData())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .addOptionsModification(options -> options.callSiteOptimizationOptions().setEnabled(false))
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("1", "2", "3", "4");
+ }
+
+ private List<byte[]> getProgramClassFileData() throws Exception {
+ return ImmutableList.of(
+ transformer(Main.class).setNest(Main.class, NestMember.class).transform(),
+ transformer(NestMember.class)
+ .setNest(Main.class, NestMember.class)
+ .setPrivate(NestMember.class.getDeclaredConstructor())
+ .setPrivate(NestMember.class.getDeclaredField("instanceField"))
+ .setPrivate(NestMember.class.getDeclaredField("staticField"))
+ .setPrivate(NestMember.class.getDeclaredMethod("instanceMethod"))
+ .setPrivate(NestMember.class.getDeclaredMethod("staticMethod"))
+ .transform());
+ }
+
+ private ExternalArtProfile getArtProfile() {
+ return ExternalArtProfile.builder()
+ .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector)
+ throws Exception {
+ ClassSubject nestMemberClassSubject = inspector.clazz(NestMember.class);
+ assertThat(nestMemberClassSubject, isPresent());
+
+ ClassSubject syntheticConstructorArgumentClassSubject =
+ inspector.clazz(
+ SyntheticItemsTestUtils.syntheticNestConstructorArgumentClass(
+ Reference.classFromClass(NestMember.class)));
+ assertThat(
+ syntheticConstructorArgumentClassSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ MethodSubject syntheticNestInstanceFieldGetterMethodSubject =
+ nestMemberClassSubject.uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
+ NestMember.class.getDeclaredField("instanceField"))
+ .getMethodName());
+ assertThat(
+ syntheticNestInstanceFieldGetterMethodSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ MethodSubject syntheticNestInstanceFieldSetterMethodSubject =
+ nestMemberClassSubject.uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestInstanceFieldSetter(
+ NestMember.class.getDeclaredField("instanceField"))
+ .getMethodName());
+ assertThat(
+ syntheticNestInstanceFieldSetterMethodSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ MethodSubject syntheticNestInstanceMethodAccessorMethodSubject =
+ nestMemberClassSubject.uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestInstanceMethodAccessor(
+ NestMember.class.getDeclaredMethod("instanceMethod"))
+ .getMethodName());
+ assertThat(
+ syntheticNestInstanceMethodAccessorMethodSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ MethodSubject syntheticNestStaticFieldGetterMethodSubject =
+ nestMemberClassSubject.uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestStaticFieldGetter(
+ NestMember.class.getDeclaredField("staticField"))
+ .getMethodName());
+ assertThat(
+ syntheticNestStaticFieldGetterMethodSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ MethodSubject syntheticNestStaticFieldSetterMethodSubject =
+ nestMemberClassSubject.uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestStaticFieldSetter(
+ NestMember.class.getDeclaredField("staticField"))
+ .getMethodName());
+ assertThat(
+ syntheticNestStaticFieldSetterMethodSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ MethodSubject syntheticNestStaticMethodAccessorMethodSubject =
+ nestMemberClassSubject.uniqueMethodWithOriginalName(
+ SyntheticItemsTestUtils.syntheticNestStaticMethodAccessor(
+ NestMember.class.getDeclaredMethod("staticMethod"))
+ .getMethodName());
+ assertThat(
+ syntheticNestStaticMethodAccessorMethodSubject,
+ notIf(isPresent(), parameters.canUseNestBasedAccesses()));
+
+ // TODO(b/265729283): Should contain the nest bridge methods and the synthesized constructor
+ // argument class.
+ profileInspector
+ .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .assertContainsNoOtherRules();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ NestMember nestMember = new NestMember();
+ nestMember.instanceField = System.currentTimeMillis() > 0 ? 1 : 0;
+ System.out.println(nestMember.instanceField);
+ System.out.println(nestMember.instanceMethod());
+ NestMember.staticField = System.currentTimeMillis() > 0 ? 3 : 0;
+ System.out.println(NestMember.staticField);
+ System.out.println(NestMember.staticMethod());
+ }
+
+ static class NestMember {
+
+ /*private*/ int instanceField;
+ /*private*/ static int staticField;
+
+ /*private*/ NestMember() {}
+
+ @NeverInline
+ /*private*/ int instanceMethod() {
+ return System.currentTimeMillis() > 0 ? 2 : 0;
+ }
+
+ @NeverInline
+ /*private*/ static int staticMethod() {
+ return System.currentTimeMillis() > 0 ? 4 : 0;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java
new file mode 100644
index 0000000..59e0636
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/OutlineOptimizationProfileRewritingTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 OutlineOptimizationProfileRewritingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addArtProfileForRewriting(getArtProfile())
+ .addOptionsModification(InlinerOptions::disableInlining)
+ .addOptionsModification(
+ options -> {
+ options.outline.threshold = 2;
+ options.outline.minSize = 2;
+ })
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!", "Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() {
+ return ExternalArtProfile.builder()
+ .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ ClassSubject outlineClassSubject =
+ inspector.clazz(SyntheticItemsTestUtils.syntheticOutlineClass(Main.class, 0));
+ assertThat(outlineClassSubject, isPresent());
+
+ MethodSubject outlineMethodSubject = outlineClassSubject.uniqueMethod();
+ assertThat(outlineMethodSubject, isPresent());
+
+ // TODO(b/265729283): Should contain the outline class and method.
+ profileInspector
+ .assertContainsMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .assertContainsNoOtherRules();
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ greet1();
+ greet2();
+ }
+
+ static void greet1() {
+ hello();
+ world();
+ }
+
+ static void greet2() {
+ hello();
+ world();
+ }
+
+ public static void hello() {
+ System.out.print("Hello");
+ }
+
+ public static void world() {
+ System.out.println(", world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java
index 3e9ef1c..704fe6a 100644
--- a/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/SyntheticLambdaClassProfileRewritingTest.java
@@ -7,17 +7,19 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
-import com.android.tools.r8.profile.art.model.ExternalArtProfileMethodRule;
-import com.android.tools.r8.profile.art.utils.ArtProfileTestingUtils;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collections;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -27,12 +29,115 @@
@RunWith(Parameterized.class)
public class SyntheticLambdaClassProfileRewritingTest extends TestBase {
+ private enum ArtProfileInputOutput {
+ MAIN_METHOD,
+ LAMBDA_BRIDGE_METHOD;
+
+ public ExternalArtProfile getArtProfile() {
+ switch (this) {
+ case MAIN_METHOD:
+ // Profile containing Main.main(). Should be rewritten to include the two lambda classes
+ // and their constructors.
+ return ExternalArtProfile.builder()
+ .addMethodRule(MethodReferenceUtils.mainMethod(Main.class))
+ .build();
+ case LAMBDA_BRIDGE_METHOD:
+ // Profile containing the two lambda implementation methods, Main.lambda$main${0,1}.
+ // Should be rewritten to include the two lambda accessibility bridge methods.
+ return ExternalArtProfile.builder()
+ .addMethodRule(
+ Reference.method(
+ Reference.classFromClass(Main.class),
+ "lambda$main$0",
+ Collections.emptyList(),
+ null))
+ .addMethodRule(
+ Reference.method(
+ Reference.classFromClass(Main.class),
+ "lambda$main$1",
+ Collections.emptyList(),
+ null))
+ .build();
+ default:
+ throw new RuntimeException();
+ }
+ }
+
+ public void inspect(
+ ArtProfileInspector profileInspector, CodeInspector inspector, TestParameters parameters) {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+
+ MethodSubject lambdaImplementationMethod0 =
+ mainClassSubject.uniqueMethodWithOriginalName("lambda$main$0");
+ assertThat(lambdaImplementationMethod0, isPresent());
+
+ MethodSubject lambdaImplementationMethod1 =
+ mainClassSubject.uniqueMethodWithOriginalName("lambda$main$1");
+ assertThat(lambdaImplementationMethod1, isPresent());
+
+ // Verify that two lambdas were synthesized when compiling to dex.
+ assertThat(
+ inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0)),
+ onlyIf(parameters.isDexRuntime(), isPresent()));
+ assertThat(
+ inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 1)),
+ onlyIf(parameters.isDexRuntime(), isPresent()));
+
+ if (parameters.isCfRuntime()) {
+ switch (this) {
+ case MAIN_METHOD:
+ profileInspector
+ .assertContainsMethodRule(mainMethodSubject)
+ .assertContainsNoOtherRules();
+ break;
+ case LAMBDA_BRIDGE_METHOD:
+ profileInspector
+ .assertContainsMethodRules(lambdaImplementationMethod0, lambdaImplementationMethod1)
+ .assertContainsNoOtherRules();
+ break;
+ default:
+ throw new RuntimeException();
+ }
+ } else {
+ assert parameters.isDexRuntime();
+ switch (this) {
+ case MAIN_METHOD:
+ // TODO(b/265729283): Since Main.main() is in the art profile, so should the two
+ // synthetic lambdas be along with their initializers. Since Main.lambda$main$*() is
+ // not in the art profile, the interface method implementation does not need to be
+ // included in the profile.
+ profileInspector
+ .assertContainsMethodRule(mainMethodSubject)
+ .assertContainsNoOtherRules();
+ break;
+ case LAMBDA_BRIDGE_METHOD:
+ // TODO(b/265729283): Since Main.lambda$main$*() is in the art profile, so should the
+ // two accessibility bridges be.
+ profileInspector
+ .assertContainsMethodRules(lambdaImplementationMethod0, lambdaImplementationMethod1)
+ .assertContainsNoOtherRules();
+ break;
+ default:
+ throw new RuntimeException();
+ }
+ }
+ }
+ }
+
@Parameter(0)
+ public ArtProfileInputOutput artProfileInputOutput;
+
+ @Parameter(1)
public TestParameters parameters;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameters(name = "{1}, {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ArtProfileInputOutput.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
@Test
@@ -40,48 +145,20 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
- .apply(
- testBuilder ->
- ArtProfileTestingUtils.addArtProfileForRewriting(
- getArtProfile(), this::inspectResidualArtProfile, testBuilder))
+ .addKeepRules(
+ "-neverinline class " + Main.class.getTypeName() + " { void lambda$main$*(); }")
+ .addArtProfileForRewriting(artProfileInputOutput.getArtProfile())
+ .enableProguardTestOptions()
.noHorizontalClassMergingOfSynthetics()
.setMinApi(parameters.getApiLevel())
.compile()
- .inspect(this::inspect)
+ .inspectResidualArtProfile(
+ (profileInspector, inspector) ->
+ artProfileInputOutput.inspect(profileInspector, inspector, parameters))
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello, world!");
}
- private ExternalArtProfile getArtProfile() {
- return ExternalArtProfile.builder()
- .addRule(
- ExternalArtProfileMethodRule.builder()
- .setMethodReference(MethodReferenceUtils.mainMethod(Main.class))
- .build())
- .build();
- }
-
- private void inspect(CodeInspector inspector) {
- // Verify that two lambdas were synthesized when compiling to dex.
- assertThat(
- inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 0)),
- onlyIf(parameters.isDexRuntime(), isPresent()));
- assertThat(
- inspector.clazz(SyntheticItemsTestUtils.syntheticLambdaClass(Main.class, 1)),
- onlyIf(parameters.isDexRuntime(), isPresent()));
- }
-
- private void inspectResidualArtProfile(ExternalArtProfile residualArtProfile) {
- if (parameters.isCfRuntime()) {
- assertEquals(getArtProfile(), residualArtProfile);
- } else {
- assert parameters.isDexRuntime();
- // TODO(b/265729283): Since Main.main() is in the art profile, so should the two synthetic
- // lambdas be.
- assertEquals(getArtProfile(), residualArtProfile);
- }
- }
-
static class Main {
public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java
new file mode 100644
index 0000000..135a501
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/VerticalClassMergingBridgeProfileRewritingTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2023, 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.profile.art.completeness;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 VerticalClassMergingBridgeProfileRewritingTest 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)
+ .addArtProfileForRewriting(getArtProfile())
+ .addOptionsModification(InlinerOptions::setOnlyForceInlining)
+ .addOptionsModification(
+ options -> options.callSiteOptimizationOptions().setEnableMethodStaticizing(false))
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectResidualArtProfile(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ private ExternalArtProfile getArtProfile() throws Exception {
+ return ExternalArtProfile.builder()
+ .addMethodRule(Reference.methodFromMethod(A.class.getDeclaredMethod("m")))
+ .build();
+ }
+
+ private void inspect(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ ClassSubject bClassSubject = inspector.clazz(B.class);
+ assertThat(bClassSubject, isPresent());
+
+ MethodSubject movedMethodSubject =
+ bClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isPrivate);
+ assertThat(movedMethodSubject, isPresent());
+
+ MethodSubject syntheticBridgeMethodSubject =
+ bClassSubject.uniqueMethodThatMatches(FoundMethodSubject::isVirtual);
+ assertThat(syntheticBridgeMethodSubject, isPresent());
+
+ // TODO(b/265729283): Should also contain the synthetic bridge method above.
+ profileInspector.assertContainsMethodRule(movedMethodSubject).assertContainsNoOtherRules();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new B().m();
+ }
+ }
+
+ static class A {
+
+ public void m() {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ static class B extends A {}
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java
index ba3a623..c22ad2a 100644
--- a/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java
+++ b/src/test/java/com/android/tools/r8/profile/art/diagnostic/HumanReadableArtProfileParserErrorDiagnosticFromArtProfileTest.java
@@ -10,20 +10,13 @@
import static org.hamcrest.CoreMatchers.equalTo;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.profile.art.ArtProfileBuilder;
-import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
-import com.android.tools.r8.profile.art.ArtProfileConsumer;
-import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
import com.android.tools.r8.profile.art.ArtProfileProvider;
-import com.android.tools.r8.profile.art.ArtProfileRuleConsumer;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.UTF8TextInputStream;
@@ -49,7 +42,7 @@
public void testD8() throws Exception {
testForD8()
.addProgramClasses(Main.class)
- .addArtProfileForRewriting(createArtProfileProvider(), createArtProfileConsumer())
+ .addArtProfileForRewriting(createArtProfileProvider())
.release()
.setMinApi(AndroidApiLevel.LATEST)
.compileWithExpectedDiagnostics(this::inspectDiagnostics);
@@ -60,7 +53,7 @@
testForR8(Backend.DEX)
.addProgramClasses(Main.class)
.addKeepMainRule(Main.class)
- .addArtProfileForRewriting(createArtProfileProvider(), createArtProfileConsumer())
+ .addArtProfileForRewriting(createArtProfileProvider())
.release()
.setMinApi(AndroidApiLevel.LATEST)
.compileWithExpectedDiagnostics(this::inspectDiagnostics);
@@ -83,34 +76,6 @@
};
}
- private ArtProfileConsumer createArtProfileConsumer() {
- return new ArtProfileConsumer() {
-
- @Override
- public ArtProfileRuleConsumer getRuleConsumer() {
- return new ArtProfileRuleConsumer() {
-
- @Override
- public void acceptClassRule(
- ClassReference classReference, ArtProfileClassRuleInfo classRuleInfo) {
- // Ignore.
- }
-
- @Override
- public void acceptMethodRule(
- MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
- // Ignore.
- }
- };
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- // Ignore.
- }
- };
- }
-
private void inspectDiagnostics(TestDiagnosticMessages diagnostics) {
diagnostics.assertErrorsMatch(
allOf(
diff --git a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfile.java b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfile.java
index 0082727..9951db3 100644
--- a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfile.java
+++ b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfile.java
@@ -4,9 +4,13 @@
package com.android.tools.r8.profile.art.model;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
+import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.function.Consumer;
/**
@@ -19,9 +23,49 @@
*/
public class ExternalArtProfile {
- private final List<ExternalArtProfileRule> rules;
+ private abstract static class ReferenceBox<R> {
- ExternalArtProfile(List<ExternalArtProfileRule> rules) {
+ private final R reference;
+
+ ReferenceBox(R reference) {
+ this.reference = reference;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ReferenceBox<?> that = (ReferenceBox<?>) o;
+ return reference.equals(that.reference);
+ }
+
+ @Override
+ public int hashCode() {
+ return reference.hashCode();
+ }
+ }
+
+ private static class ClassReferenceBox extends ReferenceBox<ClassReference> {
+
+ ClassReferenceBox(ClassReference reference) {
+ super(reference);
+ }
+ }
+
+ private static class MethodReferenceBox extends ReferenceBox<MethodReference> {
+
+ MethodReferenceBox(MethodReference reference) {
+ super(reference);
+ }
+ }
+
+ private final Map<ReferenceBox<?>, ExternalArtProfileRule> rules;
+
+ ExternalArtProfile(Map<ReferenceBox<?>, ExternalArtProfileRule> rules) {
this.rules = rules;
}
@@ -29,14 +73,34 @@
return new Builder();
}
+ public boolean containsClassRule(ClassReference classReference) {
+ return rules.containsKey(new ClassReferenceBox(classReference));
+ }
+
+ public boolean containsMethodRule(MethodReference methodReference) {
+ return rules.containsKey(new MethodReferenceBox(methodReference));
+ }
+
public void forEach(
Consumer<ExternalArtProfileClassRule> classRuleConsumer,
Consumer<ExternalArtProfileMethodRule> methodRuleConsumer) {
- for (ExternalArtProfileRule rule : rules) {
+ for (ExternalArtProfileRule rule : rules.values()) {
rule.accept(classRuleConsumer, methodRuleConsumer);
}
}
+ public ExternalArtProfileClassRule getClassRule(ClassReference classReference) {
+ return (ExternalArtProfileClassRule) rules.get(new ClassReferenceBox(classReference));
+ }
+
+ public ExternalArtProfileMethodRule getMethodRule(MethodReference methodReference) {
+ return (ExternalArtProfileMethodRule) rules.get(new MethodReferenceBox(methodReference));
+ }
+
+ public int size() {
+ return rules.size();
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -54,17 +118,46 @@
return rules.hashCode();
}
+ @Override
+ public String toString() {
+ return StringUtils.join(
+ System.lineSeparator(), rules.values(), ExternalArtProfileRule::toString);
+ }
+
public static class Builder {
- private final List<ExternalArtProfileRule> rules = new ArrayList<>();
+ private final Map<ReferenceBox<?>, ExternalArtProfileRule> rules = new LinkedHashMap<>();
+
+ public Builder addClassRule(ClassReference classReference) {
+ return addRule(
+ ExternalArtProfileClassRule.builder().setClassReference(classReference).build());
+ }
+
+ public Builder addMethodRule(MethodReference methodReference) {
+ return addMethodRule(methodReference, ArtProfileMethodRuleInfoImpl.empty());
+ }
+
+ public Builder addMethodRule(
+ MethodReference methodReference, ArtProfileMethodRuleInfo methodRuleInfo) {
+ return addRule(
+ ExternalArtProfileMethodRule.builder()
+ .setMethodReference(methodReference)
+ .setMethodRuleInfo(methodRuleInfo)
+ .build());
+ }
public Builder addRule(ExternalArtProfileRule rule) {
- rules.add(rule);
+ rule.accept(
+ classRule -> rules.put(new ClassReferenceBox(classRule.getClassReference()), classRule),
+ methodRule ->
+ rules.put(new MethodReferenceBox(methodRule.getMethodReference()), methodRule));
return this;
}
public Builder addRules(ExternalArtProfileRule... rules) {
- Collections.addAll(this.rules, rules);
+ for (ExternalArtProfileRule rule : rules) {
+ addRule(rule);
+ }
return this;
}
diff --git a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileClassRule.java b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileClassRule.java
index 84cc34b..956cd6e 100644
--- a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileClassRule.java
+++ b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileClassRule.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.references.ClassReference;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* Represents a class rule from an ART baseline profile, backed by {@link ClassReference}. Class
@@ -36,6 +37,13 @@
}
@Override
+ public boolean test(
+ Predicate<ExternalArtProfileClassRule> classRuleConsumer,
+ Predicate<ExternalArtProfileMethodRule> methodRuleConsumer) {
+ return classRuleConsumer.test(this);
+ }
+
+ @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
@@ -52,6 +60,11 @@
return classReference.hashCode();
}
+ @Override
+ public String toString() {
+ return classReference.getDescriptor();
+ }
+
public static class Builder {
private ClassReference classReference;
diff --git a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileMethodRule.java b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileMethodRule.java
index 86894b9..4eadead 100644
--- a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileMethodRule.java
+++ b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileMethodRule.java
@@ -7,7 +7,9 @@
import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfo;
import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/** Represents a method rule from an ART baseline profile, backed by {@link MethodReference}. */
public class ExternalArtProfileMethodRule extends ExternalArtProfileRule {
@@ -43,6 +45,13 @@
}
@Override
+ public boolean test(
+ Predicate<ExternalArtProfileClassRule> classRuleConsumer,
+ Predicate<ExternalArtProfileMethodRule> methodRuleConsumer) {
+ return methodRuleConsumer.test(this);
+ }
+
+ @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
@@ -60,6 +69,11 @@
return methodReference.hashCode();
}
+ @Override
+ public String toString() {
+ return methodRuleInfo.toString() + MethodReferenceUtils.toSmaliString(methodReference);
+ }
+
public static class Builder {
private MethodReference methodReference;
diff --git a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileRule.java b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileRule.java
index bd3c99e..7138589 100644
--- a/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileRule.java
+++ b/src/test/java/com/android/tools/r8/profile/art/model/ExternalArtProfileRule.java
@@ -5,10 +5,15 @@
package com.android.tools.r8.profile.art.model;
import java.util.function.Consumer;
+import java.util.function.Predicate;
public abstract class ExternalArtProfileRule {
public abstract void accept(
Consumer<ExternalArtProfileClassRule> classRuleConsumer,
Consumer<ExternalArtProfileMethodRule> methodRuleConsumer);
+
+ public abstract boolean test(
+ Predicate<ExternalArtProfileClassRule> classRuleConsumer,
+ Predicate<ExternalArtProfileMethodRule> methodRuleConsumer);
}
diff --git a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileClassRuleInspector.java b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileClassRuleInspector.java
new file mode 100644
index 0000000..eded89d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileClassRuleInspector.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2023, 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.profile.art.utils;
+
+import com.android.tools.r8.profile.art.model.ExternalArtProfileClassRule;
+
+public class ArtProfileClassRuleInspector {
+
+ private final ExternalArtProfileClassRule classRule;
+
+ ArtProfileClassRuleInspector(ExternalArtProfileClassRule classRule) {
+ this.classRule = classRule;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java
new file mode 100644
index 0000000..507dd67
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileInspector.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2023, 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.profile.art.utils;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.profile.art.model.ExternalArtProfileClassRule;
+import com.android.tools.r8.profile.art.model.ExternalArtProfileMethodRule;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class ArtProfileInspector {
+
+ private final ExternalArtProfile artProfile;
+
+ private final Set<ClassReference> checkedClassReferences = new HashSet<>();
+ private final Set<MethodReference> checkedMethodReferences = new HashSet<>();
+
+ public ArtProfileInspector(ExternalArtProfile artProfile) {
+ this.artProfile = artProfile;
+ }
+
+ public ArtProfileInspector assertEmpty() {
+ assertEquals(0, artProfile.size());
+ return this;
+ }
+
+ public ArtProfileInspector assertNotEmpty() {
+ assertNotEquals(0, artProfile.size());
+ return this;
+ }
+
+ public ArtProfileInspector assertContainsClassRule(ClassReference classReference) {
+ assertThat(artProfile, ArtProfileMatchers.containsClassRule(classReference));
+ checkedClassReferences.add(classReference);
+ return this;
+ }
+
+ public ArtProfileInspector assertContainsClassRule(ClassSubject classSubject) {
+ return assertContainsClassRule(classSubject.getFinalReference());
+ }
+
+ public ArtProfileInspector assertContainsClassRules(ClassReference... classReferences) {
+ for (ClassReference classReference : classReferences) {
+ assertContainsClassRule(classReference);
+ }
+ return this;
+ }
+
+ public ArtProfileInspector assertContainsMethodRule(MethodReference methodReference) {
+ assertThat(artProfile, ArtProfileMatchers.containsMethodRule(methodReference));
+ checkedMethodReferences.add(methodReference);
+ return this;
+ }
+
+ public ArtProfileInspector assertContainsMethodRules(MethodReference... methodReferences) {
+ for (MethodReference methodReference : methodReferences) {
+ assertContainsMethodRule(methodReference);
+ }
+ return this;
+ }
+
+ public ArtProfileInspector assertContainsMethodRule(MethodSubject methodSubject) {
+ return assertContainsMethodRule(methodSubject.getFinalReference());
+ }
+
+ public ArtProfileInspector assertContainsMethodRules(MethodSubject... methodSubjects) {
+ for (MethodSubject methodSubject : methodSubjects) {
+ assertContainsMethodRule(methodSubject);
+ }
+ return this;
+ }
+
+ public ArtProfileInspector assertContainsNoOtherRules() {
+ assertEquals(checkedClassReferences.size() + checkedMethodReferences.size(), artProfile.size());
+ return this;
+ }
+
+ public ArtProfileInspector inspectClassRule(
+ ClassReference classReference, Consumer<ArtProfileClassRuleInspector> inspector) {
+ assertContainsClassRule(classReference);
+ ExternalArtProfileClassRule classRule = artProfile.getClassRule(classReference);
+ inspector.accept(new ArtProfileClassRuleInspector(classRule));
+ return this;
+ }
+
+ public ArtProfileInspector inspectMethodRule(
+ MethodReference methodReference, Consumer<ArtProfileMethodRuleInspector> inspector) {
+ assertContainsMethodRule(methodReference);
+ ExternalArtProfileMethodRule methodRule = artProfile.getMethodRule(methodReference);
+ inspector.accept(new ArtProfileMethodRuleInspector(methodRule));
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileMatchers.java b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileMatchers.java
new file mode 100644
index 0000000..64355a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileMatchers.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2023, 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.profile.art.utils;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.profile.art.model.ExternalArtProfile;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.ClassReferenceUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class ArtProfileMatchers {
+
+ public static Matcher<ExternalArtProfile> containsClassRule(ClassReference classReference) {
+ return new TypeSafeMatcher<ExternalArtProfile>() {
+ @Override
+ protected boolean matchesSafely(ExternalArtProfile subject) {
+ return subject.containsClassRule(classReference);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(
+ "contains class rule " + ClassReferenceUtils.toSmaliString(classReference));
+ }
+
+ @Override
+ public void describeMismatchSafely(ExternalArtProfile subject, Description description) {
+ description.appendText("profile did not");
+ }
+ };
+ }
+
+ public static Matcher<ExternalArtProfile> containsClassRule(ClassSubject classSubject) {
+ assertThat(classSubject, isPresent());
+ return containsClassRule(classSubject.getFinalReference());
+ }
+
+ public static Matcher<ExternalArtProfile> containsMethodRule(MethodReference methodReference) {
+ return new TypeSafeMatcher<ExternalArtProfile>() {
+ @Override
+ protected boolean matchesSafely(ExternalArtProfile subject) {
+ return subject.containsMethodRule(methodReference);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(
+ "contains method rule " + MethodReferenceUtils.toSmaliString(methodReference));
+ }
+
+ @Override
+ public void describeMismatchSafely(ExternalArtProfile subject, Description description) {
+ description.appendText("profile did not");
+ }
+ };
+ }
+
+ public static Matcher<ExternalArtProfile> containsMethodRule(MethodSubject methodSubject) {
+ assertThat(methodSubject, isPresent());
+ return containsMethodRule(methodSubject.getFinalReference());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileMethodRuleInspector.java b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileMethodRuleInspector.java
new file mode 100644
index 0000000..fbca727
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileMethodRuleInspector.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, 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.profile.art.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.profile.art.model.ExternalArtProfileMethodRule;
+
+public class ArtProfileMethodRuleInspector {
+
+ private final ExternalArtProfileMethodRule methodRule;
+
+ ArtProfileMethodRuleInspector(ExternalArtProfileMethodRule methodRule) {
+ this.methodRule = methodRule;
+ }
+
+ public ArtProfileMethodRuleInspector assertIsHot() {
+ assertTrue(methodRule.getMethodRuleInfo().isHot());
+ return this;
+ }
+
+ public ArtProfileMethodRuleInspector assertIsStartup() {
+ assertTrue(methodRule.getMethodRuleInfo().isStartup());
+ return this;
+ }
+
+ public ArtProfileMethodRuleInspector assertIsPostStartup() {
+ assertTrue(methodRule.getMethodRuleInfo().isPostStartup());
+ return this;
+ }
+
+ public ArtProfileMethodRuleInspector assertNotHot() {
+ assertFalse(methodRule.getMethodRuleInfo().isHot());
+ return this;
+ }
+
+ public ArtProfileMethodRuleInspector assertNotStartup() {
+ assertFalse(methodRule.getMethodRuleInfo().isStartup());
+ return this;
+ }
+
+ public ArtProfileMethodRuleInspector assertNotPostStartup() {
+ assertFalse(methodRule.getMethodRuleInfo().isPostStartup());
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileTestingUtils.java b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileTestingUtils.java
index 7443e49..c86ad4d 100644
--- a/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileTestingUtils.java
+++ b/src/test/java/com/android/tools/r8/profile/art/utils/ArtProfileTestingUtils.java
@@ -5,8 +5,6 @@
package com.android.tools.r8.profile.art.utils;
import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.profile.art.ArtProfileBuilder;
import com.android.tools.r8.profile.art.ArtProfileClassRuleInfo;
@@ -23,30 +21,8 @@
public class ArtProfileTestingUtils {
- /**
- * Adds the given {@param artProfile} as an ART profile for rewriting. The residual ART profile
- * will be forwarded to the given test inspector, {@param residualArtProfileInspector}.
- */
- public static void addArtProfileForRewriting(
- ExternalArtProfile artProfile,
- Consumer<ExternalArtProfile> residualArtProfileInspector,
- R8TestBuilder<?> testBuilder) {
- testBuilder.addArtProfileForRewriting(
- createArtProfileProvider(artProfile),
- createResidualArtProfileConsumer(residualArtProfileInspector));
- }
-
- public static void addArtProfileForRewriting(
- ExternalArtProfile artProfile,
- Consumer<ExternalArtProfile> residualArtProfileInspector,
- DesugaredLibraryTestBuilder<?> testBuilder) {
- testBuilder.addL8ArtProfileForRewriting(
- createArtProfileProvider(artProfile),
- createResidualArtProfileConsumer(residualArtProfileInspector));
- }
-
// Creates an ArtProfileProvider for passing the given ART profile to a D8/L8/R8 compilation.
- private static ArtProfileProvider createArtProfileProvider(ExternalArtProfile artProfile) {
+ public static ArtProfileProvider createArtProfileProvider(ExternalArtProfile artProfile) {
return new ArtProfileProvider() {
@Override
@@ -78,7 +54,7 @@
}
// Creates an ArtProfileConsumer for accepting the residual ART profile from the compilation.
- private static ArtProfileConsumer createResidualArtProfileConsumer(
+ public static ArtProfileConsumer createResidualArtProfileConsumer(
Consumer<ExternalArtProfile> residualArtProfileInspector) {
return new ArtProfileConsumer() {
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
index 367e1b6..861e990 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceStackTraceFunctionalCompositionTest.java
@@ -193,8 +193,6 @@
.setMappingSupplier(
ProguardMappingSupplier.builder()
.setProguardMapProducer(ProguardMapProducer.fromPath(mappingFile))
- // TODO(b/241763080): Remove when stable.
- .setAllowExperimental(true)
.build())
.build());
return retracedLines;
@@ -246,8 +244,6 @@
.addProgramFiles(r8Input)
.addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
.addKeepRuleFiles(MAIN_KEEP)
- // TODO(b/241763080): Remove when stable version is default.
- .enableExperimentalMapFileVersion()
.allowUnusedProguardConfigurationRules()
.addDontObfuscate()
.compile()
@@ -264,10 +260,10 @@
.setMode(CompilationMode.RELEASE)
.addProgramFiles(r8Input)
.addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
- .enableExperimentalMapFileVersion()
- // TODO(b/241763080): Enable CF PC test mapping for this compilation.
.addOptionsModification(
- options -> options.mappingComposeOptions().enableExperimentalMappingComposition = true)
+ options -> {
+ assertTrue(options.mappingComposeOptions().enableExperimentalMappingComposition);
+ })
.apply(
b ->
b.getBuilder()
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 1b67cfc..cf968ea 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -3,18 +3,28 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
+import static com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring.NEST_ACCESS_FIELD_GET_NAME_PREFIX;
+import static com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring.NEST_ACCESS_FIELD_PUT_NAME_PREFIX;
+import static com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring.NEST_ACCESS_METHOD_NAME_PREFIX;
+import static com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring.NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX;
+import static com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring.NEST_ACCESS_STATIC_METHOD_NAME_PREFIX;
+import static com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring.NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX;
import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
import static org.hamcrest.CoreMatchers.containsString;
+import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.Collections;
import org.hamcrest.Matcher;
public class SyntheticItemsTestUtils {
@@ -53,6 +63,15 @@
originalMethod.getMethodDescriptor());
}
+ public static MethodReference syntheticInvokeSpecialMethod(Method method) {
+ MethodReference originalMethod = Reference.methodFromMethod(method);
+ return Reference.method(
+ originalMethod.getHolderClass(),
+ InvokeSpecialToSelfDesugaring.INVOKE_SPECIAL_BRIDGE_PREFIX + method.getName(),
+ originalMethod.getFormalTypes(),
+ originalMethod.getReturnType());
+ }
+
public static MethodReference syntheticBackportWithForwardingMethod(
ClassReference clazz, int id, MethodReference method) {
// For backports with forwarding the backported method is not static, so the original method
@@ -127,6 +146,75 @@
originalMethod.getMethodDescriptor());
}
+ public static ClassReference syntheticNestConstructorArgumentClass(
+ ClassReference classReference) {
+ return Reference.classFromDescriptor(
+ SyntheticNaming.createDescriptor(
+ "", naming.INIT_TYPE_ARGUMENT, classReference.getBinaryName(), ""));
+ }
+
+ public static MethodReference syntheticNestInstanceFieldGetter(Field field) {
+ FieldReference fieldReference = Reference.fieldFromField(field);
+ return Reference.method(
+ fieldReference.getHolderClass(),
+ NEST_ACCESS_FIELD_GET_NAME_PREFIX + field.getName(),
+ Collections.emptyList(),
+ fieldReference.getFieldType());
+ }
+
+ public static MethodReference syntheticNestInstanceFieldSetter(Field field) {
+ FieldReference fieldReference = Reference.fieldFromField(field);
+ return Reference.method(
+ fieldReference.getHolderClass(),
+ NEST_ACCESS_FIELD_PUT_NAME_PREFIX + field.getName(),
+ ImmutableList.of(fieldReference.getFieldType()),
+ null);
+ }
+
+ public static MethodReference syntheticNestInstanceMethodAccessor(Method method) {
+ MethodReference originalMethod = Reference.methodFromMethod(method);
+ return Reference.methodFromDescriptor(
+ originalMethod.getHolderClass(),
+ NEST_ACCESS_METHOD_NAME_PREFIX + method.getName(),
+ originalMethod.getMethodDescriptor());
+ }
+
+ public static MethodReference syntheticNestStaticFieldGetter(Field field) {
+ FieldReference fieldReference = Reference.fieldFromField(field);
+ return Reference.method(
+ fieldReference.getHolderClass(),
+ NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX + field.getName(),
+ Collections.emptyList(),
+ fieldReference.getFieldType());
+ }
+
+ public static MethodReference syntheticNestStaticFieldSetter(Field field) {
+ FieldReference fieldReference = Reference.fieldFromField(field);
+ return Reference.method(
+ fieldReference.getHolderClass(),
+ NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX + field.getName(),
+ ImmutableList.of(fieldReference.getFieldType()),
+ null);
+ }
+
+ public static MethodReference syntheticNestStaticMethodAccessor(Method method) {
+ MethodReference originalMethod = Reference.methodFromMethod(method);
+ return Reference.methodFromDescriptor(
+ originalMethod.getHolderClass(),
+ NEST_ACCESS_STATIC_METHOD_NAME_PREFIX + method.getName(),
+ originalMethod.getMethodDescriptor());
+ }
+
+ public static ClassReference syntheticEnumUnboxingLocalUtilityClass(Class<?> clazz) {
+ return Reference.classFromTypeName(
+ clazz.getTypeName() + naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS.getDescriptor());
+ }
+
+ public static ClassReference syntheticEnumUnboxingSharedUtilityClass(Class<?> clazz) {
+ return Reference.classFromTypeName(
+ clazz.getTypeName() + naming.ENUM_UNBOXING_SHARED_UTILITY_CLASS.getDescriptor());
+ }
+
public static boolean isEnumUnboxingSharedUtilityClass(ClassReference reference) {
return SyntheticNaming.isSynthetic(reference, null, naming.ENUM_UNBOXING_SHARED_UTILITY_CLASS);
}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 7742119..4adbc9a 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -502,14 +502,22 @@
return setAccessFlags(method, MethodAccessFlags::setBridge);
}
+ public ClassFileTransformer setPrivate(Constructor<?> constructor) {
+ return setAccessFlags(constructor, ClassFileTransformer::setPrivate);
+ }
+
public ClassFileTransformer setPrivate(Field field) {
- return setAccessFlags(
- field,
- accessFlags -> {
- accessFlags.setPrivate();
- accessFlags.unsetProtected();
- accessFlags.unsetPublic();
- });
+ return setAccessFlags(field, ClassFileTransformer::setPrivate);
+ }
+
+ public ClassFileTransformer setPrivate(Method method) {
+ return setAccessFlags(method, ClassFileTransformer::setPrivate);
+ }
+
+ private static void setPrivate(AccessFlags<?> accessFlags) {
+ accessFlags.unsetPublic();
+ accessFlags.unsetProtected();
+ accessFlags.setPrivate();
}
public ClassFileTransformer setPublic(Method method) {
@@ -522,16 +530,6 @@
});
}
- public ClassFileTransformer setPrivate(Method method) {
- return setAccessFlags(
- method,
- accessFlags -> {
- accessFlags.unsetPublic();
- accessFlags.unsetProtected();
- accessFlags.setPrivate();
- });
- }
-
public ClassFileTransformer setSynthetic(Method method) {
return setAccessFlags(method, AccessFlags::setSynthetic);
}