Merge commit 'f2c3df4e1cecb6215c901479c1ada04b95dfa336' into dev-release
diff --git a/compatibility-faq.md b/compatibility-faq.md
index 3ba23c8..8ca09d5 100644
--- a/compatibility-faq.md
+++ b/compatibility-faq.md
@@ -129,7 +129,7 @@
the fields to be renamed by R8 to the same name, but GSON serialization will
work as expected.
-### GSON with full mode
+### GSON
GSON uses type tokens to serialize and deserialize generic types.
@@ -138,7 +138,7 @@
The anonymous class will have a generic signature argument of `List<String>` to
the super type `TypeToken` that is reflective read for serialization. It
is therefore necessary to keep both the `Signature` attribute, the
-`com.google.gson.reflect.TypeToken` class and all sub-types:
+`com.google.gson.reflect.TypeToken` class and all sub-types.
```
-keepattributes Signature
@@ -146,6 +146,9 @@
-keep class * extends com.google.gson.reflect.TypeToken
```
+This is also needed for R8 in compat mode since multiple optimizations will
+remove the generic signature such as class merging and argument removal.
+
## Retrofit
### Objects instantiated with Retrofit's `create()` method are always replaced with `null`
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 6f1fde5..6fd3574 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -153,6 +153,11 @@
}
@Override
+ public AppView<?> getAppView() {
+ return appView;
+ }
+
+ @Override
public void allocateRegisters() {
computeNeedsRegister();
ImmutableList<BasicBlock> blocks = computeLivenessInformation();
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 5a0ec34..a5d6098 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.dex;
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isVivifiedType;
+
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
@@ -149,6 +151,9 @@
private void keepClass(DexType type) {
DexType baseType = type.lookupBaseType(appView.dexItemFactory());
+ if (isVivifiedType(baseType)) {
+ return;
+ }
toKeep.putIfAbsent(baseType, new KeepStruct());
}
diff --git a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
index dcb6bb9..10d8c1f 100644
--- a/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/ProguardKeepRuleDiagnostic.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.errors;
import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
-/** Base class for diagnostics related to proguard keep rules. */
-public abstract class ProguardKeepRuleDiagnostic implements Diagnostic {}
+/** Base interface for diagnostics related to proguard keep rules. */
+@Keep
+public interface ProguardKeepRuleDiagnostic extends Diagnostic {}
diff --git a/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java b/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
index 396f88b..2d2b326 100644
--- a/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/UnusedProguardKeepRuleDiagnostic.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.shaking.ProguardConfigurationRule;
@Keep
-public class UnusedProguardKeepRuleDiagnostic extends ProguardKeepRuleDiagnostic {
+public class UnusedProguardKeepRuleDiagnostic implements ProguardKeepRuleDiagnostic {
private final ProguardConfigurationRule rule;
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
new file mode 100644
index 0000000..acd327d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+/**
+ * A keep condition is the content of an item in the set of preconditions.
+ *
+ * <p>It can be trivially true, or represent some program item pattern that must be present in the
+ * program residual. When an condition is denoted by a program item, the condition also specifies
+ * the extent of the item for which it is predicated on. The extent is given by a "usage kind" that
+ * can be either its "symbolic reference" or its "actual use".
+ */
+public final class KeepCondition {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepItemPattern itemPattern;
+
+ private Builder() {}
+
+ public Builder setItem(KeepItemPattern itemPattern) {
+ this.itemPattern = itemPattern;
+ return this;
+ }
+
+ public KeepCondition build() {
+ return new KeepCondition(itemPattern);
+ }
+ }
+
+ private final KeepItemPattern itemPattern;
+
+ private KeepCondition(KeepItemPattern itemPattern) {
+ this.itemPattern = itemPattern;
+ }
+
+ public KeepItemPattern getItemPattern() {
+ return itemPattern;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
new file mode 100644
index 0000000..6a227fd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Set of consequences of a keep edge.
+ *
+ * <p>The consequences are "targets" described by item patterns along with "keep options" which
+ * detail what aspects of the items must be retained.
+ *
+ * <p>The consequences come into effect if the preconditions of an edge are met.
+ */
+public final class KeepConsequences {
+
+ public static class Builder {
+
+ private List<KeepTarget> targets = new ArrayList<>();
+
+ private Builder() {}
+
+ public Builder addTarget(KeepTarget target) {
+ targets.add(target);
+ return this;
+ }
+
+ public KeepConsequences build() {
+ return new KeepConsequences(targets);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final List<KeepTarget> targets;
+
+ private KeepConsequences(List<KeepTarget> targets) {
+ this.targets = targets;
+ }
+
+ public boolean isEmpty() {
+ return targets.isEmpty();
+ }
+
+ public void forEachTarget(Consumer<KeepTarget> fn) {
+ targets.forEach(fn);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
new file mode 100644
index 0000000..f61483a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+/**
+ * An edge in the keep graph.
+ *
+ * <p>An edge describes a set of preconditions and a set of consequences. If the preconditions are
+ * met, then the consequences are put into effect.
+ */
+public final class KeepEdge {
+
+ public static class Builder {
+ private KeepPreconditions preconditions = KeepPreconditions.always();
+ private KeepConsequences consequences;
+
+ private Builder() {}
+
+ public Builder setPreconditions(KeepPreconditions preconditions) {
+ this.preconditions = preconditions;
+ return this;
+ }
+
+ public Builder setConsequences(KeepConsequences consequences) {
+ this.consequences = consequences;
+ return this;
+ }
+
+ public KeepEdge build() {
+ if (consequences.isEmpty()) {
+ throw new KeepEdgeException("KeepEdge must have non-empty set of consequences.");
+ }
+ return new KeepEdge(preconditions, consequences);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final KeepPreconditions preconditions;
+ private final KeepConsequences consequences;
+
+ private KeepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
+ this.preconditions = preconditions;
+ this.consequences = consequences;
+ }
+
+ public KeepPreconditions getPreconditions() {
+ return preconditions;
+ }
+
+ public KeepConsequences getConsequences() {
+ return consequences;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
new file mode 100644
index 0000000..3345c8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public class KeepEdgeException extends RuntimeException {
+
+ public KeepEdgeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
new file mode 100644
index 0000000..17e543f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+/** Pattern for matching the "extends" or "implements" clause of a class. */
+public abstract class KeepExtendsPattern {
+
+ public static KeepExtendsPattern any() {
+ return KeepExtendsAnyPattern.getInstance();
+ }
+
+ public static class Builder {
+
+ private KeepExtendsPattern pattern;
+
+ private Builder() {}
+
+ public Builder any() {
+ pattern = KeepExtendsAnyPattern.getInstance();
+ return this;
+ }
+
+ public Builder classPattern(KeepQualifiedClassNamePattern pattern) {
+ this.pattern = new KeepExtendsClassPattern(pattern);
+ return this;
+ }
+ }
+
+ private static class KeepExtendsAnyPattern extends KeepExtendsPattern {
+
+ private static KeepExtendsAnyPattern INSTANCE = null;
+
+ public static KeepExtendsAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepExtendsAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+ }
+
+ private static class KeepExtendsClassPattern extends KeepExtendsPattern {
+
+ private final KeepQualifiedClassNamePattern pattern;
+
+ public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public boolean isAny() {
+ return pattern.isAny();
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private KeepExtendsPattern() {}
+
+ public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
new file mode 100644
index 0000000..04e2697
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public class KeepFieldPattern extends KeepMemberPattern {
+
+ private KeepFieldPattern() {}
+
+ public boolean isAnyField() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
new file mode 100644
index 0000000..3c6f947
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * A pattern for matching items in the program.
+ *
+ * <p>An item pattern can be any item, or it can describe a family of classes or a family of members
+ * on a classes.
+ *
+ * <p>A pattern cannot describe both a class *and* a member of a class. Either it is a pattern on
+ * classes or it is a pattern on members. The distinction is defined by having a "none" member
+ * pattern.
+ */
+public abstract class KeepItemPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepItemPattern any() {
+ return KeepItemAnyPattern.getInstance();
+ }
+
+ public static class Builder {
+
+ private KeepQualifiedClassNamePattern classNamePattern;
+ private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
+ private KeepMembersPattern membersPattern = KeepMembersPattern.none();
+
+ private Builder() {}
+
+ public Builder any() {
+ classNamePattern = KeepQualifiedClassNamePattern.any();
+ extendsPattern = KeepExtendsPattern.any();
+ membersPattern = KeepMembersPattern.all();
+ return this;
+ }
+
+ public Builder setClassPattern(KeepQualifiedClassNamePattern qualifiedClassNamePattern) {
+ this.classNamePattern = qualifiedClassNamePattern;
+ return this;
+ }
+
+ public Builder setExtendsPattern(KeepExtendsPattern extendsPattern) {
+ this.extendsPattern = extendsPattern;
+ return this;
+ }
+
+ public Builder setMembersPattern(KeepMembersPattern membersPattern) {
+ this.membersPattern = membersPattern;
+ return this;
+ }
+
+ public KeepItemPattern build() {
+ if (classNamePattern == null) {
+ throw new KeepEdgeException("Class pattern must define a class name pattern.");
+ }
+ if (classNamePattern.isAny() && extendsPattern.isAny() && membersPattern.isAll()) {
+ return KeepItemPattern.any();
+ }
+ return new KeepClassPattern(classNamePattern, extendsPattern, membersPattern);
+ }
+ }
+
+ private static class KeepItemAnyPattern extends KeepItemPattern {
+
+ private static KeepItemAnyPattern INSTANCE = null;
+
+ public static KeepItemAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepItemAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) {
+ return onAny.get();
+ }
+ }
+
+ public static class KeepClassPattern extends KeepItemPattern {
+
+ private final KeepQualifiedClassNamePattern qualifiedClassPattern;
+ private final KeepExtendsPattern extendsPattern;
+ private final KeepMembersPattern membersPattern;
+ // TODO: class annotations
+
+ private KeepClassPattern(
+ KeepQualifiedClassNamePattern qualifiedClassPattern,
+ KeepExtendsPattern extendsPattern,
+ KeepMembersPattern membersPattern) {
+ this.qualifiedClassPattern = qualifiedClassPattern;
+ this.extendsPattern = extendsPattern;
+ this.membersPattern = membersPattern;
+ }
+
+ @Override
+ public boolean isAny() {
+ return qualifiedClassPattern.isAny() && extendsPattern.isAny() && membersPattern.isAll();
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) {
+ if (isAny()) {
+ return onAny.get();
+ } else {
+ return onItem.apply(this);
+ }
+ }
+
+ public KeepQualifiedClassNamePattern getClassNamePattern() {
+ return qualifiedClassPattern;
+ }
+
+ public KeepExtendsPattern getExtendsPattern() {
+ return extendsPattern;
+ }
+
+ public KeepMembersPattern getMembersPattern() {
+ return membersPattern;
+ }
+ }
+
+ public abstract boolean isAny();
+
+ public abstract <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem);
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
new file mode 100644
index 0000000..73bae7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public abstract class KeepMemberPattern {
+
+ public static KeepMemberPattern anyMember() {
+ return KeepMemberAnyPattern.getInstance();
+ }
+
+ private static class KeepMemberAnyPattern extends KeepMemberPattern {
+ private static KeepMemberAnyPattern INSTANCE = null;
+
+ public static KeepMemberAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMemberAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAnyMember() {
+ return true;
+ }
+ }
+
+ public boolean isAnyMember() {
+ return false;
+ }
+
+ abstract static class Builder<T extends Builder<T>> {
+
+ public abstract T self();
+
+ Builder() {}
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
new file mode 100644
index 0000000..6d3f93e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import com.android.tools.r8.errors.Unimplemented;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class KeepMembersPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepMembersPattern none() {
+ return KeepMembersNonePattern.getInstance();
+ }
+
+ public static KeepMembersPattern all() {
+ return KeepMembersAllPattern.getInstance();
+ }
+
+ public static class Builder {
+
+ private boolean anyMethod = false;
+ private boolean anyField = false;
+ private List<KeepMethodPattern> methods = new ArrayList<>();
+ private List<KeepFieldPattern> fields = new ArrayList<>();
+
+ public Builder addMethodPattern(KeepMethodPattern methodPattern) {
+ if (anyMethod) {
+ return this;
+ }
+ if (methodPattern.isAnyMethod()) {
+ methods.clear();
+ anyMethod = true;
+ }
+ methods.add(methodPattern);
+ return this;
+ }
+
+ public Builder addFieldPattern(KeepFieldPattern fieldPattern) {
+ if (anyField) {
+ return this;
+ }
+ if (fieldPattern.isAnyField()) {
+ fields.clear();
+ anyField = true;
+ }
+ fields.add(fieldPattern);
+ return this;
+ }
+
+ public KeepMembersPattern build() {
+ if (methods.isEmpty() && fields.isEmpty()) {
+ return KeepMembersPattern.none();
+ }
+ if (anyMethod && anyField) {
+ return KeepMembersPattern.all();
+ }
+ return new KeepMembersSomePattern(methods, fields);
+ }
+ }
+
+ private static class KeepMembersAllPattern extends KeepMembersPattern {
+
+ private static KeepMembersAllPattern INSTANCE = null;
+
+ public static KeepMembersAllPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMembersAllPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAll() {
+ return true;
+ }
+
+ @Override
+ public boolean isNone() {
+ return true;
+ }
+
+ @Override
+ public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
+ throw new Unimplemented("Should this include all and none?");
+ }
+ }
+
+ private static class KeepMembersNonePattern extends KeepMembersPattern {
+
+ private static KeepMembersNonePattern INSTANCE = null;
+
+ public static KeepMembersNonePattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMembersNonePattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAll() {
+ return false;
+ }
+
+ @Override
+ public boolean isNone() {
+ return true;
+ }
+
+ @Override
+ public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
+ throw new Unimplemented("Should this include all and none?");
+ }
+ }
+
+ private static class KeepMembersSomePattern extends KeepMembersPattern {
+
+ private final List<KeepMethodPattern> methods;
+ private final List<KeepFieldPattern> fields;
+
+ private KeepMembersSomePattern(List<KeepMethodPattern> methods, List<KeepFieldPattern> fields) {
+ assert !methods.isEmpty() || !fields.isEmpty();
+ this.methods = methods;
+ this.fields = fields;
+ }
+
+ @Override
+ public boolean isAll() {
+ // Since there is at least one none-all field or method this is not a match all.
+ return false;
+ }
+
+ @Override
+ public boolean isNone() {
+ // Since there is at least one field or method this is not a match none.
+ return false;
+ }
+
+ @Override
+ public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
+ fields.forEach(onField);
+ methods.forEach(onMethod);
+ }
+ }
+
+ private KeepMembersPattern() {}
+
+ public abstract boolean isAll();
+
+ public abstract boolean isNone();
+
+ public abstract void forEach(
+ Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod);
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
new file mode 100644
index 0000000..7f83937
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+// TODO: finish this.
+public class KeepMethodAccessPattern {
+
+ public static KeepMethodAccessPattern any() {
+ return new KeepMethodAccessPattern();
+ }
+
+ public boolean isAny() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
new file mode 100644
index 0000000..ada6701
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class KeepMethodNamePattern {
+
+ public static KeepMethodNamePattern any() {
+ return KeepMethodNameAnyPattern.getInstance();
+ }
+
+ public static KeepMethodNamePattern initializer() {
+ return new KeepMethodNameExactPattern("<init>");
+ }
+
+ public static KeepMethodNamePattern exact(String methodName) {
+ return new KeepMethodNameExactPattern(methodName);
+ }
+
+ private KeepMethodNamePattern() {}
+
+ public boolean isAny() {
+ return match(() -> true, ignore -> false);
+ }
+
+ public boolean isExact() {
+ return match(() -> false, ignore -> true);
+ }
+ ;
+
+ public abstract <T> T match(Supplier<T> onAny, Function<String, T> onExact);
+
+ private static class KeepMethodNameAnyPattern extends KeepMethodNamePattern {
+ private static KeepMethodNameAnyPattern INSTANCE = null;
+
+ public static KeepMethodNameAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMethodNameAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onAny, Function<String, T> onExact) {
+ return onAny.get();
+ }
+ }
+
+ private static class KeepMethodNameExactPattern extends KeepMethodNamePattern {
+ private final String name;
+
+ public KeepMethodNameExactPattern(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onAny, Function<String, T> onExact) {
+ return onExact.apply(name);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
new file mode 100644
index 0000000..4f42202
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class KeepMethodParametersPattern {
+
+ public static KeepMethodParametersPattern any() {
+ return Any.getInstance();
+ }
+
+ public static KeepMethodParametersPattern none() {
+ return None.getInstance();
+ }
+
+ private KeepMethodParametersPattern() {}
+
+ public abstract <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList);
+
+ private static class None extends KeepMethodParametersPattern {
+ private static None INSTANCE = null;
+
+ public static None getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new None();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) {
+ return onList.apply(Collections.emptyList());
+ }
+ }
+
+ private static class Any extends KeepMethodParametersPattern {
+ private static Any INSTANCE = null;
+
+ public static Any getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new Any();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) {
+ return onAny.get();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
new file mode 100644
index 0000000..5db7d26
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public final class KeepMethodPattern extends KeepMemberPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends KeepMemberPattern.Builder<Builder> {
+
+ private KeepMethodAccessPattern accessPattern = KeepMethodAccessPattern.any();
+ private KeepMethodNamePattern namePattern = null;
+ private KeepMethodReturnTypePattern returnTypePattern = KeepMethodReturnTypePattern.any();
+ private KeepMethodParametersPattern parametersPattern = KeepMethodParametersPattern.any();
+
+ private Builder() {}
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ public Builder setAccessPattern(KeepMethodAccessPattern accessPattern) {
+ this.accessPattern = accessPattern;
+ return self();
+ }
+
+ public Builder setNamePattern(KeepMethodNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return self();
+ }
+
+ public Builder setReturnTypeVoid() {
+ returnTypePattern = KeepMethodReturnTypePattern.voidType();
+ return self();
+ }
+
+ public Builder setParametersPattern(KeepMethodParametersPattern parametersPattern) {
+ this.parametersPattern = parametersPattern;
+ return self();
+ }
+
+ public KeepMethodPattern build() {
+ if (namePattern == null) {
+ throw new KeepEdgeException("Method pattern must declar a name pattern");
+ }
+ return new KeepMethodPattern(
+ accessPattern, namePattern, returnTypePattern, parametersPattern);
+ }
+ }
+
+ private final KeepMethodAccessPattern accessPattern;
+ private final KeepMethodNamePattern namePattern;
+ private final KeepMethodReturnTypePattern returnTypePattern;
+ private final KeepMethodParametersPattern parametersPattern;
+
+ private KeepMethodPattern(
+ KeepMethodAccessPattern accessPattern,
+ KeepMethodNamePattern namePattern,
+ KeepMethodReturnTypePattern returnTypePattern,
+ KeepMethodParametersPattern parametersPattern) {
+ this.accessPattern = accessPattern;
+ this.namePattern = namePattern;
+ this.returnTypePattern = returnTypePattern;
+ this.parametersPattern = parametersPattern;
+ }
+
+ public boolean isAnyMethod() {
+ return false;
+ }
+
+ public KeepMethodAccessPattern getAccessPattern() {
+ return accessPattern;
+ }
+
+ public KeepMethodNamePattern getNamePattern() {
+ return namePattern;
+ }
+
+ public KeepMethodReturnTypePattern getReturnTypePattern() {
+ return returnTypePattern;
+ }
+
+ public KeepMethodParametersPattern getParametersPattern() {
+ return parametersPattern;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
new file mode 100644
index 0000000..4e7b5b9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public abstract class KeepMethodReturnTypePattern {
+
+ private static SomeType ANY_TYPE_INSTANCE = null;
+
+ public static KeepMethodReturnTypePattern any() {
+ if (ANY_TYPE_INSTANCE == null) {
+ ANY_TYPE_INSTANCE = new SomeType(KeepTypePattern.any());
+ }
+ return ANY_TYPE_INSTANCE;
+ }
+
+ public static KeepMethodReturnTypePattern voidType() {
+ return VoidType.getInstance();
+ }
+
+ public abstract <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType);
+
+ private static class VoidType extends KeepMethodReturnTypePattern {
+ private static VoidType INSTANCE = null;
+
+ public static VoidType getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new VoidType();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) {
+ return onVoid.get();
+ }
+ }
+
+ private static class SomeType extends KeepMethodReturnTypePattern {
+
+ private final KeepTypePattern typePattern;
+
+ private SomeType(KeepTypePattern typePattern) {
+ this.typePattern = typePattern;
+ }
+
+ @Override
+ public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) {
+ return onType.apply(typePattern);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
new file mode 100644
index 0000000..9fb521c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+public final class KeepOptions {
+
+ public enum KeepOption {
+ SHRINKING,
+ OPTIMIZING,
+ OBFUSCATING,
+ ACCESS_MODIFING,
+ }
+
+ public static KeepOptions keepAll() {
+ if (ALLOW_NONE_INSTANCE == null) {
+ ALLOW_NONE_INSTANCE = new KeepOptions(true, Collections.emptySet());
+ }
+ return ALLOW_NONE_INSTANCE;
+ }
+
+ public static Builder allowBuilder() {
+ return new Builder(true);
+ }
+
+ public static Builder disallowBuilder() {
+ return new Builder(false);
+ }
+
+ public static KeepOptions allow(KeepOption... options) {
+ return allowBuilder().addAll(options).build();
+ }
+
+ public static KeepOptions disallow(KeepOption... options) {
+ return disallowBuilder().addAll(options).build();
+ }
+
+ public static class Builder {
+ public final boolean allowIfSet;
+ public Set<KeepOption> options = SetUtils.newIdentityHashSet();
+
+ private Builder(boolean allowIfSet) {
+ this.allowIfSet = allowIfSet;
+ }
+
+ public Builder add(KeepOption option) {
+ options.add(option);
+ return this;
+ }
+
+ public Builder addAll(KeepOption... options) {
+ return addAll(Arrays.asList(options));
+ }
+
+ public Builder addAll(Collection<KeepOption> options) {
+ this.options.addAll(options);
+ return this;
+ }
+
+ public KeepOptions build() {
+ if (options.isEmpty()) {
+ if (allowIfSet) {
+ return keepAll();
+ }
+ throw new KeepEdgeException("Invalid keep options that disallow nothing.");
+ }
+ if (options.size() == KeepOption.values().length) {
+ if (!allowIfSet) {
+ return keepAll();
+ }
+ throw new KeepEdgeException("Invalid keep options that allow everything.");
+ }
+ return new KeepOptions(allowIfSet, ImmutableSet.copyOf(options));
+ }
+ }
+
+ private static KeepOptions ALLOW_NONE_INSTANCE = null;
+
+ private final boolean allowIfSet;
+ private final Set<KeepOption> options;
+
+ private KeepOptions(boolean allowIfSet, Set<KeepOption> options) {
+ this.allowIfSet = allowIfSet;
+ this.options = options;
+ }
+
+ public boolean isAllowed(KeepOption option) {
+ return options.contains(option) == allowIfSet;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
new file mode 100644
index 0000000..10db385
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
@@ -0,0 +1,180 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public abstract class KeepPackagePattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepPackagePattern any() {
+ return KeepPackageAnyPattern.getInstance();
+ }
+
+ public static KeepPackagePattern top() {
+ return KeepPackageTopPattern.getInstance();
+ }
+
+ public static KeepPackagePattern exact(String fullPackage) {
+ return KeepPackagePattern.builder().exact(fullPackage).build();
+ }
+
+ public static class Builder {
+
+ private KeepPackagePattern pattern;
+
+ public Builder any() {
+ pattern = KeepPackageAnyPattern.getInstance();
+ return this;
+ }
+
+ public Builder top() {
+ pattern = KeepPackageTopPattern.getInstance();
+ return this;
+ }
+
+ public Builder exact(String fullPackage) {
+ pattern =
+ fullPackage.isEmpty()
+ ? KeepPackagePattern.top()
+ : new KeepPackageExactPattern(fullPackage);
+ return this;
+ }
+
+ public KeepPackagePattern build() {
+ if (pattern == null) {
+ throw new KeepEdgeException("Invalid package pattern: null");
+ }
+ return pattern;
+ }
+ }
+
+ private static final class KeepPackageAnyPattern extends KeepPackagePattern {
+
+ private static KeepPackageAnyPattern INSTANCE = null;
+
+ public static KeepPackageAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepPackageAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ private KeepPackageAnyPattern() {}
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+
+ @Override
+ public boolean isTop() {
+ return false;
+ }
+
+ @Override
+ public boolean isExact() {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+ }
+
+ private static final class KeepPackageTopPattern extends KeepPackageExactPattern {
+
+ private static KeepPackageTopPattern INSTANCE = null;
+
+ public static KeepPackageTopPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepPackageTopPattern();
+ }
+ return INSTANCE;
+ }
+
+ private KeepPackageTopPattern() {
+ super("");
+ }
+
+ @Override
+ public boolean isAny() {
+ return false;
+ }
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
+ }
+
+ public static class KeepPackageExactPattern extends KeepPackagePattern {
+
+ private final String fullPackage;
+
+ private KeepPackageExactPattern(String fullPackage) {
+ assert fullPackage != null;
+ this.fullPackage = fullPackage;
+ // TODO: Verify valid package identifiers.
+ }
+
+ @Override
+ public boolean isAny() {
+ return false;
+ }
+
+ @Override
+ public boolean isTop() {
+ return fullPackage.equals("");
+ }
+
+ @Override
+ public boolean isExact() {
+ return true;
+ }
+
+ @Override
+ public KeepPackageExactPattern asExact() {
+ return this;
+ }
+
+ public String getExactPackageAsString() {
+ return fullPackage;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepPackageExactPattern that = (KeepPackageExactPattern) o;
+ return fullPackage.equals(that.fullPackage);
+ }
+
+ @Override
+ public int hashCode() {
+ return fullPackage.hashCode();
+ }
+ }
+
+ public abstract boolean isAny();
+
+ public abstract boolean isTop();
+
+ public abstract boolean isExact();
+
+ public KeepPackageExactPattern asExact() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
new file mode 100644
index 0000000..f3083df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class KeepPreconditions {
+
+ public abstract void forEach(Consumer<KeepCondition> fn);
+
+ public static class Builder {
+
+ private List<KeepCondition> preconditions = new ArrayList<>();
+
+ private Builder() {}
+
+ public Builder addCondition(KeepCondition condition) {
+ preconditions.add(condition);
+ return this;
+ }
+
+ public KeepPreconditions build() {
+ return preconditions.isEmpty()
+ ? KeepPreconditions.always()
+ : new KeepPreconditionsSome(preconditions);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepPreconditions always() {
+ return KeepPreconditionsAlways.getInstance();
+ }
+
+ public abstract boolean isAlways();
+
+ private static class KeepPreconditionsAlways extends KeepPreconditions {
+
+ private static KeepPreconditionsAlways INSTANCE = null;
+
+ public static KeepPreconditionsAlways getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepPreconditionsAlways();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAlways() {
+ return true;
+ }
+
+ @Override
+ public void forEach(Consumer<KeepCondition> fn) {
+ // Empty.
+ }
+ }
+
+ private static class KeepPreconditionsSome extends KeepPreconditions {
+
+ private final List<KeepCondition> preconditions;
+
+ private KeepPreconditionsSome(List<KeepCondition> preconditions) {
+ this.preconditions = preconditions;
+ }
+
+ @Override
+ public boolean isAlways() {
+ return false;
+ }
+
+ @Override
+ public void forEach(Consumer<KeepCondition> fn) {
+ preconditions.forEach(fn);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
new file mode 100644
index 0000000..9485580
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+import java.util.Objects;
+
+public final class KeepQualifiedClassNamePattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepQualifiedClassNamePattern any() {
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(KeepPackagePattern.any())
+ .setNamePattern(KeepUnqualfiedClassNamePattern.any())
+ .build();
+ }
+
+ public static KeepQualifiedClassNamePattern exact(String qualifiedClassName) {
+ int pkgSeparator = qualifiedClassName.lastIndexOf('.');
+ if (pkgSeparator == 0) {
+ throw new KeepEdgeException("Unexpected '.' at index 0 in '" + qualifiedClassName + "'");
+ }
+ if (pkgSeparator > 0) {
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(
+ KeepPackagePattern.exact(qualifiedClassName.substring(0, pkgSeparator)))
+ .setNamePattern(
+ KeepUnqualfiedClassNamePattern.exact(qualifiedClassName.substring(pkgSeparator + 1)))
+ .build();
+ }
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(KeepPackagePattern.top())
+ .setNamePattern(KeepUnqualfiedClassNamePattern.exact(qualifiedClassName))
+ .build();
+ }
+
+ public static class Builder {
+
+ private KeepPackagePattern packagePattern;
+ private KeepUnqualfiedClassNamePattern namePattern;
+
+ private Builder() {}
+
+ public Builder setPackagePattern(KeepPackagePattern packagePattern) {
+ this.packagePattern = packagePattern;
+ return this;
+ }
+
+ public Builder setNamePattern(KeepUnqualfiedClassNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return this;
+ }
+
+ public KeepQualifiedClassNamePattern build() {
+ return new KeepQualifiedClassNamePattern(packagePattern, namePattern);
+ }
+ }
+
+ private final KeepPackagePattern packagePattern;
+ private final KeepUnqualfiedClassNamePattern namePattern;
+
+ public KeepQualifiedClassNamePattern(
+ KeepPackagePattern packagePattern, KeepUnqualfiedClassNamePattern namePattern) {
+ assert packagePattern != null;
+ assert namePattern != null;
+ this.packagePattern = packagePattern;
+ this.namePattern = namePattern;
+ }
+
+ public boolean isAny() {
+ return packagePattern.isAny() && namePattern.isAny();
+ }
+
+ public KeepPackagePattern getPackagePattern() {
+ return packagePattern;
+ }
+
+ public KeepUnqualfiedClassNamePattern getNamePattern() {
+ return namePattern;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepQualifiedClassNamePattern that = (KeepQualifiedClassNamePattern) o;
+ return packagePattern.equals(that.packagePattern) && namePattern.equals(that.namePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packagePattern.hashCode(), namePattern.hashCode());
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
new file mode 100644
index 0000000..a63a34a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public class KeepTarget {
+
+ public static KeepTarget any() {
+ return KeepTarget.builder().setItem(KeepItemPattern.any()).build();
+ }
+
+ public static class Builder {
+
+ private KeepItemPattern item;
+ private KeepOptions options = KeepOptions.keepAll();
+
+ private Builder() {}
+
+ public Builder setItem(KeepItemPattern item) {
+ this.item = item;
+ return this;
+ }
+
+ public Builder setOptions(KeepOptions options) {
+ this.options = options;
+ return this;
+ }
+
+ public KeepTarget build() {
+ if (item == null) {
+ throw new KeepEdgeException("Target must define an item pattern");
+ }
+ return new KeepTarget(item, options);
+ }
+ }
+
+ private final KeepItemPattern item;
+ private final KeepOptions options;
+
+ private KeepTarget(KeepItemPattern item, KeepOptions options) {
+ this.item = item;
+ this.options = options;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public KeepItemPattern getItem() {
+ return item;
+ }
+
+ public KeepOptions getOptions() {
+ return options;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
new file mode 100644
index 0000000..eff9e9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public abstract class KeepTypePattern {
+
+ public static KeepTypePattern any() {
+ return Any.getInstance();
+ }
+
+ private static class Any extends KeepTypePattern {
+ private static Any INSTANCE = null;
+
+ public static Any getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new Any();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+ }
+
+ public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
new file mode 100644
index 0000000..ab38ed2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+public abstract class KeepUnqualfiedClassNamePattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepUnqualfiedClassNamePattern any() {
+ return KeepClassNameAnyPattern.getInstance();
+ }
+
+ public static KeepUnqualfiedClassNamePattern exact(String className) {
+ return builder().exact(className).build();
+ }
+
+ public static class Builder {
+
+ private KeepUnqualfiedClassNamePattern pattern;
+
+ public Builder any() {
+ pattern = KeepClassNameAnyPattern.getInstance();
+ return this;
+ }
+
+ public Builder exact(String className) {
+ pattern = new KeepClassNameExactPattern(className);
+ return this;
+ }
+
+ public KeepUnqualfiedClassNamePattern build() {
+ if (pattern == null) {
+ throw new KeepEdgeException("Invalid class name pattern: null");
+ }
+ return pattern;
+ }
+ }
+
+ private static class KeepClassNameAnyPattern extends KeepUnqualfiedClassNamePattern {
+
+ private static KeepClassNameAnyPattern INSTANCE = null;
+
+ public static KeepClassNameAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepClassNameAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ private KeepClassNameAnyPattern() {}
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+
+ @Override
+ public boolean isExact() {
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+ }
+
+ public static class KeepClassNameExactPattern extends KeepUnqualfiedClassNamePattern {
+
+ private final String className;
+
+ private KeepClassNameExactPattern(String className) {
+ assert className != null;
+ this.className = className;
+ }
+
+ @Override
+ public boolean isAny() {
+ return false;
+ }
+
+ @Override
+ public boolean isExact() {
+ return true;
+ }
+
+ @Override
+ public KeepClassNameExactPattern asExact() {
+ return this;
+ }
+
+ public String getExactNameAsString() {
+ return className;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepClassNameExactPattern that = (KeepClassNameExactPattern) o;
+ return className.equals(that.className);
+ }
+
+ @Override
+ public int hashCode() {
+ return className.hashCode();
+ }
+ }
+
+ public abstract boolean isAny();
+
+ public abstract boolean isExact();
+
+ public KeepClassNameExactPattern asExact() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
new file mode 100644
index 0000000..6c24ec1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
@@ -0,0 +1,262 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.keeprules;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepFieldPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern.KeepClassPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMembersPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodAccessPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodParametersPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepMethodReturnTypePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepOptions;
+import com.android.tools.r8.experimental.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.experimental.keepanno.ast.KeepPackagePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepPreconditions;
+import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class KeepRuleExtractor {
+
+ private final Consumer<String> ruleConsumer;
+
+ public KeepRuleExtractor(Consumer<String> ruleConsumer) {
+ this.ruleConsumer = ruleConsumer;
+ }
+
+ public void extract(KeepEdge edge) {
+ List<ItemRule> consequentRules = getConsequentRules(edge.getConsequences());
+ printConditionalRules(consequentRules, edge.getPreconditions());
+ }
+
+ private List<ItemRule> getConsequentRules(KeepConsequences consequences) {
+ List<ItemRule> consequentItems = new ArrayList<>();
+ consequences.forEachTarget(target -> consequentItems.add(new ItemRule(target)));
+ return consequentItems;
+ }
+
+ private void printConditionalRules(
+ List<ItemRule> consequentRules, KeepPreconditions preconditions) {
+ boolean[] hasAtLeastOneConditionalClause = new boolean[1];
+ preconditions.forEach(
+ condition -> {
+ // The usage kind for a predicate is not expressible in keep rules, so it is
+ // ignored.
+ condition
+ .getItemPattern()
+ .match(
+ () -> {
+ // If the conditions is "any" then we ignore it for now (identity of
+ // conjunction).
+ return null;
+ },
+ conditionItem -> {
+ hasAtLeastOneConditionalClause[0] = true;
+ consequentRules.forEach(
+ consequentItem -> {
+ // Since conjunctions are not supported in keep rules, we expand them into
+ // disjunctions so conservatively we keep the consequences if any one of
+ // the preconditions hold.
+ StringBuilder builder = new StringBuilder();
+ if (!consequentItem.isMemberConsequent()
+ || !conditionItem
+ .getClassNamePattern()
+ .equals(consequentItem.getHolderPattern())) {
+ builder.append("-if ");
+ printClassItem(builder, conditionItem);
+ builder.append(' ');
+ }
+ printConsequentRule(builder, consequentItem);
+ ruleConsumer.accept(builder.toString());
+ });
+ return null;
+ });
+ });
+ assert !(preconditions.isAlways() && hasAtLeastOneConditionalClause[0]);
+ if (!hasAtLeastOneConditionalClause[0]) {
+ // If there are no preconditions, print each consequent as is.
+ consequentRules.forEach(
+ r -> ruleConsumer.accept(printConsequentRule(new StringBuilder(), r).toString()));
+ }
+ }
+
+ private static StringBuilder printConsequentRule(StringBuilder builder, ItemRule rule) {
+ if (rule.isMemberConsequent()) {
+ builder.append("-keepclassmembers");
+ } else {
+ builder.append("-keep");
+ }
+ for (KeepOption option : KeepOption.values()) {
+ if (rule.options.isAllowed(option)) {
+ builder.append(",allow").append(getOptionString(option));
+ }
+ }
+ return builder.append(" ").append(rule.getKeepRuleForItem());
+ }
+
+ private static StringBuilder printClassItem(
+ StringBuilder builder, KeepClassPattern clazzPattern) {
+ builder.append("class ");
+ printClassName(builder, clazzPattern.getClassNamePattern());
+ if (!clazzPattern.getExtendsPattern().isAny()) {
+ throw new Unimplemented();
+ }
+ KeepMembersPattern members = clazzPattern.getMembersPattern();
+ if (members.isNone()) {
+ return builder;
+ }
+ if (members.isAll()) {
+ return builder.append(" { *; }");
+ }
+ builder.append(" {");
+ members.forEach(
+ field -> printField(builder.append(' '), field),
+ method -> printMethod(builder.append(' '), method));
+ return builder.append(" }");
+ }
+
+ private static StringBuilder printField(StringBuilder builder, KeepFieldPattern field) {
+ if (field.isAnyField()) {
+ return builder.append("<fields>;");
+ } else {
+ throw new Unimplemented();
+ }
+ }
+
+ 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 StringBuilder printParameters(
+ StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
+ return parametersPattern.match(
+ () -> builder.append("(***)"),
+ list -> StringUtils.append(builder, list, ", ", BraceType.PARENS));
+ }
+
+ private static StringBuilder printMethodName(
+ StringBuilder builder, KeepMethodNamePattern namePattern) {
+ return namePattern.match(() -> builder.append("*"), builder::append);
+ }
+
+ private static StringBuilder printReturnType(
+ StringBuilder builder, KeepMethodReturnTypePattern returnTypePattern) {
+ return returnTypePattern.match(
+ () -> builder.append("void"), typePattern -> printType(builder, typePattern));
+ }
+
+ private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) {
+ if (typePattern.isAny()) {
+ return builder.append("*");
+ }
+ throw new Unimplemented();
+ }
+
+ 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 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.asExact().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_MODIFING:
+ return "accessmodification";
+ default:
+ throw new Unimplemented();
+ }
+ }
+
+ private static class ItemRule {
+ private final KeepTarget target;
+ private final KeepOptions options;
+ private String ruleLine = null;
+
+ public ItemRule(KeepTarget target) {
+ this.target = target;
+ this.options = target.getOptions();
+ }
+
+ public boolean isMemberConsequent() {
+ return target.getItem().match(() -> false, clazz -> !clazz.getMembersPattern().isNone());
+ }
+
+ public KeepQualifiedClassNamePattern getHolderPattern() {
+ return target
+ .getItem()
+ .match(KeepQualifiedClassNamePattern::any, KeepClassPattern::getClassNamePattern);
+ }
+
+ public String getKeepRuleForItem() {
+ if (ruleLine == null) {
+ ruleLine =
+ target
+ .getItem()
+ .match(
+ () -> "class * { *; }",
+ clazz -> printClassItem(new StringBuilder(), clazz).toString());
+ }
+ return ruleLine;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 85bc79c..cd7b5b9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -132,13 +132,11 @@
BasicBlock nextBlock;
public DexBuilder(
- AppView<?> appView,
IRCode ir,
BytecodeMetadataProvider bytecodeMetadataProvider,
RegisterAllocator registerAllocator,
InternalOptions options) {
this(
- appView,
ir,
bytecodeMetadataProvider,
registerAllocator,
@@ -147,14 +145,13 @@
}
public DexBuilder(
- AppView<?> appView,
IRCode ir,
BytecodeMetadataProvider bytecodeMetadataProvider,
RegisterAllocator registerAllocator,
InternalOptions options,
MethodConversionOptions conversionOptions) {
assert ir == null || conversionOptions == ir.getConversionOptions();
- this.appView = appView;
+ this.appView = registerAllocator.getAppView();
this.ir = ir;
this.bytecodeMetadataBuilder = BytecodeMetadata.builder(bytecodeMetadataProvider);
this.registerAllocator = registerAllocator;
@@ -173,7 +170,6 @@
DexBuilder builder =
new DexBuilder(
null,
- null,
BytecodeMetadataProvider.empty(),
allocator,
allocator.options(),
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
index cbd0d06..cf59a7e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -51,8 +51,7 @@
RuntimeWorkaroundCodeRewriter.workaroundExceptionTargetingLoopHeaderBug(code, options);
// Perform register allocation.
RegisterAllocator registerAllocator = performRegisterAllocation(code, method, timing);
- return new DexBuilder(appView, code, bytecodeMetadataProvider, registerAllocator, options)
- .build();
+ return new DexBuilder(code, bytecodeMetadataProvider, registerAllocator, options).build();
}
private RegisterAllocator performRegisterAllocation(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index b1ea459..7f0fd6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1155,12 +1155,17 @@
if (field == null) {
return Reason.INVALID_FIELD_PUT;
}
- DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context());
- if (dexClass == null) {
+ DexProgramClass holderClass =
+ appView.programDefinitionFor(field.getHolderType(), code.context());
+ if (holderClass == null) {
return Reason.INVALID_FIELD_PUT;
}
if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) {
- return Reason.ELIGIBLE;
+ // TODO(b/249752942): The requirement to be inside an initializer of the enum can be relaxed
+ // if we support puts.
+ return context.getHolder() == enumClass && context.getDefinition().isInstanceInitializer()
+ ? Reason.ELIGIBLE
+ : Reason.ASSIGNMENT_OUTSIDE_INIT;
}
// The put value has to be of the field type.
if (field.getReference().type.toBaseType(factory) != enumClass.type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index f2fbb4c..9a88b2e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -221,6 +221,12 @@
int fallthroughBlockIndex = switchInsn.getFallthroughBlockIndex();
if (ordinalToTargetMap.size() < switchInsn.numberOfKeys()) {
+ if (block.numberOfNormalSuccessors() != switchInsn.numberOfKeys() + 1) {
+ // This can happen in extremely rare cases where several switch targets are the same
+ // block (See b/231804008).
+ // TODO(b/249052389): Support removing switch map for such switches.
+ continue;
+ }
// There is at least one dead switch case. This can happen when some dependencies use
// different versions of the same enum.
int numberOfNormalSuccessors = switchInsn.numberOfKeys() + 1;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 63fc7fd..3e17a0a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -34,6 +34,7 @@
public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
+ public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER =
new StringReason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 90eb8a9..15c762b 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -640,6 +640,11 @@
return appView.options();
}
+ @Override
+ public AppView<?> getAppView() {
+ return appView;
+ }
+
private ImmutableList<BasicBlock> computeLivenessInformation() {
ImmutableList<BasicBlock> blocks = code.numberInstructions();
liveAtEntrySets = code.computeLiveAtEntrySets();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 28fe1f9..6c2221c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.regalloc;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Value;
@@ -19,6 +20,8 @@
InternalOptions options();
+ AppView<?> getAppView();
+
void mergeBlocks(BasicBlock kept, BasicBlock removed);
boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second);
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 9ddddfe..100d61a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -54,6 +54,7 @@
private final String inlineClassUnderlyingPropertyName;
private final KotlinTypeInfo inlineClassUnderlyingType;
private final int jvmFlags;
+ private final String companionObjectName;
// List of tracked assignments of kotlin metadata.
private final KotlinMetadataMembersTracker originalMembersWithKotlinInfo;
@@ -78,7 +79,8 @@
String inlineClassUnderlyingPropertyName,
KotlinTypeInfo inlineClassUnderlyingType,
KotlinMetadataMembersTracker originalMembersWithKotlinInfo,
- int jvmFlags) {
+ int jvmFlags,
+ String companionObjectName) {
this.flags = flags;
this.name = name;
this.nameCanBeSynthesizedFromClassOrAnonymousObjectOrigin =
@@ -100,6 +102,7 @@
this.inlineClassUnderlyingType = inlineClassUnderlyingType;
this.originalMembersWithKotlinInfo = originalMembersWithKotlinInfo;
this.jvmFlags = jvmFlags;
+ this.companionObjectName = companionObjectName;
}
public static KotlinClassInfo create(
@@ -154,7 +157,7 @@
keepByteCode,
extensionInformation,
originalMembersWithKotlinInfo);
- setCompanionObject(kmClass, hostClass, reporter);
+ String companionObjectName = setCompanionObject(kmClass, hostClass, reporter);
KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory);
boolean nameCanBeDeducedFromClassOrOrigin =
kmClass.name.equals(
@@ -183,7 +186,8 @@
kmClass.getInlineClassUnderlyingPropertyName(),
KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
originalMembersWithKotlinInfo,
- JvmExtensionsKt.getJvmFlags(kmClass));
+ JvmExtensionsKt.getJvmFlags(kmClass),
+ companionObjectName);
}
private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -228,19 +232,20 @@
return superTypeInfos.build();
}
- private static void setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) {
+ private static String setCompanionObject(KmClass kmClass, DexClass hostClass, Reporter reporter) {
String companionObjectName = kmClass.getCompanionObject();
if (companionObjectName == null) {
- return;
+ return companionObjectName;
}
for (DexEncodedField field : hostClass.fields()) {
if (field.getReference().name.toString().equals(companionObjectName)) {
field.setKotlinMemberInfo(new KotlinCompanionInfo(companionObjectName));
- return;
+ return companionObjectName;
}
}
reporter.warning(
KotlinMetadataDiagnostic.missingCompanionObject(hostClass, companionObjectName));
+ return companionObjectName;
}
@Override
@@ -284,6 +289,7 @@
rewritten |= !name.equals(rewrittenName);
}
// Find a companion object.
+ boolean foundCompanion = false;
for (DexEncodedField field : clazz.fields()) {
if (field.getKotlinInfo().isCompanion()) {
rewritten |=
@@ -291,8 +297,14 @@
.getKotlinInfo()
.asCompanion()
.rewrite(kmClass, field.getReference(), appView.getNamingLens());
+ foundCompanion = true;
}
}
+ // If we did not find a companion but it was there on input we have to emit a new metadata
+ // object.
+ if (!foundCompanion && companionObjectName != null) {
+ rewritten = true;
+ }
// Take all not backed constructors because we will never find them in definitions.
for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
rewritten |= constructorInfo.rewrite(kmClass, null, appView);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1b77e82..09c87e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -137,6 +137,7 @@
import com.android.tools.r8.shaking.EnqueuerWorklist.TraceStaticFieldWriteAction;
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
+import com.android.tools.r8.shaking.KeepMethodInfo.Joiner;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
import com.android.tools.r8.shaking.RootSetUtils.RootSet;
@@ -943,9 +944,19 @@
if (clazz.hasDefaultInitializer()) {
ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
if (forceProguardCompatibility) {
- workList.enqueueMarkMethodKeptAction(
- defaultInitializer,
- graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+ Joiner joiner = KeepMethodInfo.newEmptyJoiner();
+ for (ProguardKeepRuleBase rule : rules) {
+ if (!rule.getType().equals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS)) {
+ joiner.addRule(rule);
+ }
+ }
+ if (!joiner.getRules().isEmpty()) {
+ workList.enqueueMarkMethodKeptAction(
+ defaultInitializer,
+ graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+ applyMinimumKeepInfoWhenLiveOrTargeted(
+ defaultInitializer, joiner.disallowOptimization());
+ }
}
if (clazz.isExternalizable(appView)) {
workList.enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness);
@@ -2049,7 +2060,8 @@
|| appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
: "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
- assert !appView.unboxedEnums().isUnboxedEnum(clazz);
+ assert !appView.unboxedEnums().isUnboxedEnum(clazz)
+ : "Enum " + clazz.toSourceString() + " has been unboxed but is still in the program.";
if (options.isGeneratingClassFiles() && clazz.hasPermittedSubclassAttributes()) {
throw new CompilationError(
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 f07a328..6bf0a8f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1988,8 +1988,7 @@
public boolean allowInvokeErrors = false;
public boolean allowUnnecessaryDontWarnWildcards = true;
public boolean allowUnusedDontWarnRules = true;
- public boolean reportUnusedProguardConfigurationRules =
- System.getProperty("com.android.tools.r8.reportUnusedProguardConfigurationRules") != null;
+ public boolean reportUnusedProguardConfigurationRules = true;
public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
public boolean alwaysUsePessimisticRegisterAllocation = false;
public boolean enableCheckCastAndInstanceOfRemoval = true;
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 4a22769..7d2ea31 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -6,8 +6,10 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser;
import com.android.tools.r8.origin.Origin;
@@ -249,14 +251,28 @@
|| warnings.stream()
.allMatch(warn -> warn.getDiagnosticMessage().contains("org.testng.Assert")));
List<Diagnostic> infos = diagnosticsMessages.getInfos();
- // The rewriting confuses the generic signatures in some methods. Such signatures are never
- // used by tools (they use the non library desugared version) and are stripped when compiling
- // with R8 anyway.
- // TODO(b/243483320): Investigate the Invalid signature.
- assertTrue(
- infos.isEmpty()
- || infos.stream()
- .allMatch(info -> info.getDiagnosticMessage().contains("Invalid signature ")));
+ for (Diagnostic info : infos) {
+ // The rewriting confuses the generic signatures in some methods. Such signatures are never
+ // used by tools (they use the non library desugared version) and are stripped when compiling
+ // with R8 anyway.
+ if (info instanceof UnusedProguardKeepRuleDiagnostic) {
+ // The default keep rules on desugared library may be unused. They should all be defined
+ // with keepclassmembers.
+ if (info.getDiagnosticMessage().contains("keepclassmembers")) {
+ continue;
+ }
+ // We allow info regarding the extended version of desugared library for JDK11 testing.
+ if (info.getDiagnosticMessage().contains("org.testng.")) {
+ continue;
+ }
+ fail("Unexpected unused proguard keep rule diagnostic: " + info.getDiagnosticMessage());
+ }
+ // TODO(b/243483320): Investigate the Invalid signature.
+ if (info.getDiagnosticMessage().contains("Invalid signature ")) {
+ continue;
+ }
+ fail("Unexpected info diagnostic: " + info.getDiagnosticMessage());
+ }
}
private L8Command.Builder addProgramClassFileData(L8Command.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index 73dbba2..fd3e16d 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.compilerapi.assertionconfiguration.AssertionConfigurationTest;
import com.android.tools.r8.compilerapi.classconflictresolver.ClassConflictResolverTest;
import com.android.tools.r8.compilerapi.desugardependencies.DesugarDependenciesTest;
+import com.android.tools.r8.compilerapi.diagnostics.ProguardKeepRuleDiagnosticsApiTest;
import com.android.tools.r8.compilerapi.diagnostics.UnsupportedFeaturesDiagnosticApiTest;
import com.android.tools.r8.compilerapi.globalsynthetics.GlobalSyntheticsTest;
import com.android.tools.r8.compilerapi.inputdependencies.InputDependenciesTest;
@@ -56,7 +57,8 @@
ImmutableList.of(
ArtProfilesForRewritingApiTest.ApiTest.class,
StartupProfileApiTest.ApiTest.class,
- ClassConflictResolverTest.ApiTest.class);
+ ClassConflictResolverTest.ApiTest.class,
+ ProguardKeepRuleDiagnosticsApiTest.ApiTest.class);
private final TemporaryFolder temp;
diff --git a/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java
new file mode 100644
index 0000000..fedb449
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/diagnostics/ProguardKeepRuleDiagnosticsApiTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.diagnostics;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.DiagnosticsLevel;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.errors.ProguardKeepRuleDiagnostic;
+import com.android.tools.r8.errors.UnusedProguardKeepRuleDiagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import java.util.Collections;
+import org.junit.Test;
+
+public class ProguardKeepRuleDiagnosticsApiTest extends CompilerApiTestRunner {
+
+ public ProguardKeepRuleDiagnosticsApiTest(TestParameters parameters) {
+ super(parameters);
+ }
+
+ @Override
+ public Class<? extends CompilerApiTest> binaryTestClass() {
+ return ApiTest.class;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ ApiTest test = new ApiTest(ApiTest.PARAMETERS);
+ runTest(test::runR8);
+ }
+
+ private void runTest(ThrowingConsumer<DiagnosticsHandler, Exception> test) throws Exception {
+ TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+ test.accept(diagnostics);
+ diagnostics
+ .assertOnlyWarnings()
+ // Check the diagnostic is an instance of both types.
+ .assertWarningsMatch(
+ allOf(
+ diagnosticType(UnusedProguardKeepRuleDiagnostic.class),
+ diagnosticMessage(containsString("does not match anything"))))
+ .assertWarningsMatch(
+ allOf(
+ diagnosticType(ProguardKeepRuleDiagnostic.class),
+ diagnosticMessage(containsString("does not match anything"))));
+ }
+
+ public static class ApiTest extends CompilerApiTest {
+
+ public ApiTest(Object parameters) {
+ super(parameters);
+ }
+
+ public void runR8(DiagnosticsHandler handler) throws Exception {
+ R8.run(
+ R8Command.builder(
+ new DiagnosticsHandler() {
+ @Override
+ public DiagnosticsLevel modifyDiagnosticsLevel(
+ DiagnosticsLevel level, Diagnostic diagnostic) {
+ if (diagnostic instanceof ProguardKeepRuleDiagnostic
+ || diagnostic instanceof UnusedProguardKeepRuleDiagnostic) {
+ return handler.modifyDiagnosticsLevel(DiagnosticsLevel.WARNING, diagnostic);
+ }
+ return handler.modifyDiagnosticsLevel(level, diagnostic);
+ }
+
+ @Override
+ public void error(Diagnostic error) {
+ handler.error(error);
+ }
+
+ @Override
+ public void warning(Diagnostic warning) {
+ handler.warning(warning);
+ }
+
+ @Override
+ public void info(Diagnostic info) {
+ handler.info(info);
+ }
+ })
+ .addClassProgramData(getBytesForClass(getMockClass()), Origin.unknown())
+ .addProguardConfiguration(getKeepMainRules(getMockClass()), Origin.unknown())
+ .addProguardConfiguration(
+ Collections.singletonList("-keep class NotPresent {}"), Origin.unknown())
+ .addLibraryFiles(getJava8RuntimeJar())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setMinApiLevel(1)
+ .build());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runR8(
+ new DiagnosticsHandler() {
+ @Override
+ public void warning(Diagnostic warning) {
+ // ignore the warnings.
+ }
+ });
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
index 2bd0f31..40942f7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryWarningTest.java
@@ -24,14 +24,32 @@
@RunWith(Parameterized.class)
public class DesugaredLibraryWarningTest extends DesugaredLibraryTestBase {
- private static final String FUNCTION_KEEP =
- "-keep class j$.util.function.Function$-CC {\n"
- + " j$.util.function.Function $default$compose(j$.util.function.Function,"
- + " j$.util.function.Function);\n"
- + " j$.util.function.Function $default$andThen(j$.util.function.Function,"
- + " j$.util.function.Function);\n"
- + "}\n"
- + "-keep class j$.util.function.Function { *; }";
+ private static final String getFunctionKeep(String prefix) {
+ return "-keep class j$.util.function.Function$-CC {\n"
+ + " "
+ + prefix
+ + ".util.function.Function $default$compose("
+ + prefix
+ + ".util.function.Function,"
+ + " "
+ + prefix
+ + ".util.function.Function);\n"
+ + " "
+ + prefix
+ + ".util.function.Function $default$andThen("
+ + prefix
+ + ".util.function.Function,"
+ + " "
+ + prefix
+ + ".util.function.Function);\n"
+ + "}\n"
+ + "-keep class "
+ + prefix
+ + ".util.function.Function { *; }";
+ }
+
+ private static final String FUNCTION_KEEP_J$ = getFunctionKeep("j$");
+ private static final String FUNCTION_KEEP_JAVA = getFunctionKeep("java");
private final TestParameters parameters;
private final CompilationSpecification compilationSpecification;
@@ -60,18 +78,24 @@
.apply(
l8TestBuilder ->
libraryDesugaringSpecification.configureL8TestBuilder(
- l8TestBuilder, compilationSpecification.isL8Shrink(), FUNCTION_KEEP))
+ l8TestBuilder,
+ compilationSpecification.isL8Shrink(),
+ libraryDesugaringSpecification.hasEmulatedInterfaceDesugaring(parameters)
+ ? libraryDesugaringSpecification.hasJDollarFunction(parameters)
+ ? FUNCTION_KEEP_J$
+ : FUNCTION_KEEP_JAVA
+ : ""))
.compile()
.inspectDiagnosticMessages(
diagnosticsHandler -> {
+ diagnosticsHandler.assertNoErrors();
if (libraryDesugaringSpecification != JDK8) {
- diagnosticsHandler.assertNoErrors();
diagnosticsHandler.assertAllWarningsMatch(
diagnosticMessage(containsString("Specification conversion")));
} else {
-
- diagnosticsHandler.assertNoMessages();
+ diagnosticsHandler.assertNoWarnings();
}
+ diagnosticsHandler.assertNoInfos();
});
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
index 542c71e..058f6cf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
@@ -61,9 +61,6 @@
.anyMatch(
kr ->
kr.contains("-keep class " + prefix + ".util.function.Consumer")));
- // TODO(b/158635415): Don't generate keep rules targeting items outside desugared
- // library.
- assertTrue(keepRule.stream().anyMatch(kr -> kr.contains("-keep class java.util")));
}
});
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
index 26e16bc..104876d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathTest.java
@@ -66,8 +66,11 @@
@Test
public void test() throws Throwable {
testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
- .addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**")
- .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**")
+ .applyIf(
+ libraryDesugaringSpecification.hasNioFileDesugaring(parameters),
+ b ->
+ b.addL8KeepRules("-keepnames class j$.desugar.sun.nio.fs.**")
+ .addL8KeepRules("-keepnames class j$.nio.file.FileSystem**"))
.addInnerClasses(PathTest.class)
.addKeepMainRule(TestClass.class)
.compile()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
new file mode 100644
index 0000000..d2fe030
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInEnumFieldTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/** This is a regression for b/247146910. */
+@RunWith(Parameterized.class)
+public class EnumInEnumFieldTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumInEnumFieldTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(EnumInEnumFieldTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0");
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumInEnumFieldTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .addEnumUnboxingInspector(
+ inspector -> {
+ inspector.assertNotUnboxed(MyEnum.class);
+ inspector.assertUnboxed(OtherEnum.class);
+ })
+ .addNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("0");
+ }
+
+ @NeverClassInline
+ public enum OtherEnum {
+ C,
+ D;
+ }
+
+ @NeverClassInline
+ public enum MyEnum {
+ A,
+ B;
+
+ public OtherEnum otherEnum;
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ set(System.currentTimeMillis() > 0 ? OtherEnum.C : OtherEnum.D);
+ System.out.println(MyEnum.A.otherEnum.ordinal());
+ }
+
+ public static void set(OtherEnum otherEnum) {
+ MyEnum.A.otherEnum = otherEnum;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
new file mode 100644
index 0000000..55cdede
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceInsideAndOutsideTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a variant of a regression test for b/247146910 where the instance field on the enum is
+ * unrelated to enum unboxing.
+ */
+@RunWith(Parameterized.class)
+public class EnumInstanceFieldReferenceInsideAndOutsideTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumInstanceFieldReferenceInsideAndOutsideTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @Test
+ public void testEnumUnboxingAllowNotPrunedEnums() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumInstanceFieldReferenceInsideAndOutsideTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .addNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @NeverClassInline
+ public enum MyEnum {
+ A(10),
+ B(20);
+
+ public int instanceValue;
+
+ MyEnum(int instanceValue) {
+ this.instanceValue = instanceValue;
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ set(System.currentTimeMillis() > 0 ? 42 : 0);
+ System.out.println(MyEnum.B.instanceValue);
+ }
+
+ public static void set(int newValue) {
+ MyEnum.B.instanceValue = newValue;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
new file mode 100644
index 0000000..4dac1af
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInstanceFieldReferenceOutsideTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a variant of a regression test for b/247146910 where the instance field on the enum is
+ * unrelated to enum unboxing.
+ */
+@RunWith(Parameterized.class)
+public class EnumInstanceFieldReferenceOutsideTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final EnumKeepRules enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public EnumInstanceFieldReferenceOutsideTest(
+ TestParameters parameters, boolean enumValueOptimization, EnumKeepRules enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @Test
+ public void testEnumUnboxingAllowNotPrunedEnums() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumInstanceFieldReferenceOutsideTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepRules(enumKeepRules.getKeepRules())
+ .addEnumUnboxingInspector(inspector -> inspector.assertNotUnboxed(MyEnum.class))
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .addNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ @NeverClassInline
+ public enum MyEnum {
+ A,
+ B;
+
+ public int instanceValue = 1;
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ set(System.currentTimeMillis() > 0 ? 42 : 0);
+ System.out.println(MyEnum.B.instanceValue);
+ }
+
+ public static void set(int newValue) {
+ MyEnum.B.instanceValue = newValue;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
new file mode 100644
index 0000000..d69d055
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.keepanno.ast;
+
+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.experimental.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.experimental.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeApiTest extends TestBase {
+
+ private static String CLASS = "com.example.Foo";
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public KeepEdgeApiTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ public static String extract(KeepEdge edge) {
+ StringBuilder builder = new StringBuilder();
+ KeepRuleExtractor extractor = new KeepRuleExtractor(rule -> builder.append(rule).append('\n'));
+ extractor.extract(edge);
+ return builder.toString();
+ }
+
+ @Test
+ public void testKeepAll() {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(KeepTarget.builder().setItem(KeepItemPattern.any()).build())
+ .build())
+ .build();
+ assertEquals(StringUtils.unixLines("-keep class * { *; }"), extract(edge));
+ }
+
+ @Test
+ public void testSoftPinViaDisallow() {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(
+ KeepTarget.builder()
+ .setItem(KeepItemPattern.any())
+ .setOptions(KeepOptions.disallow(KeepOption.OPTIMIZING))
+ .build())
+ .build())
+ .build();
+ // Disallow will issue the full inverse of the known options, e.g., 'allowaccessmodification'.
+ assertEquals(
+ StringUtils.unixLines(
+ "-keep,allowshrinking,allowobfuscation,allowaccessmodification class * { *; }"),
+ extract(edge));
+ }
+
+ @Test
+ public void testSoftPinViaAllow() {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(
+ KeepTarget.builder()
+ .setItem(KeepItemPattern.any())
+ .setOptions(
+ KeepOptions.allow(KeepOption.OBFUSCATING, KeepOption.SHRINKING))
+ .build())
+ .build())
+ .build();
+ // Allow is just the ordered list of options.
+ assertEquals(
+ StringUtils.unixLines("-keep,allowshrinking,allowobfuscation class * { *; }"),
+ extract(edge));
+ }
+
+ @Test
+ public void testKeepClass() throws Exception {
+ 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));
+ }
+
+ @Test
+ public void testKeepInitIfReferenced() throws Exception {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder()
+ .setItem(classItem(CLASS))
+ .build())
+ .build())
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(
+ target(
+ buildClassItem(CLASS)
+ .setMembersPattern(defaultInitializerPattern())
+ .build()))
+ .build())
+ .build();
+ assertEquals(
+ StringUtils.unixLines("-keepclassmembers class " + CLASS + " { void <init>(); }"),
+ extract(edge));
+ }
+
+ @Test
+ public void testKeepInstanceIfReferenced() throws Exception {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder()
+ .setItem(classItem(CLASS))
+ .build())
+ .build())
+ .setConsequences(KeepConsequences.builder().addTarget(target(classItem(CLASS))).build())
+ .build();
+ assertEquals(
+ StringUtils.unixLines("-if class " + CLASS + " -keep class " + CLASS), extract(edge));
+ }
+
+ @Test
+ public void testKeepInstanceAndInitIfReferenced() throws Exception {
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder()
+ .setItem(classItem(CLASS))
+ .build())
+ .build())
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(target(classItem(CLASS)))
+ .addTarget(
+ target(
+ buildClassItem(CLASS)
+ .setMembersPattern(defaultInitializerPattern())
+ .build()))
+ .build())
+ .build();
+ assertEquals(
+ StringUtils.unixLines(
+ "-if class " + CLASS + " -keep class " + CLASS,
+ "-keepclassmembers class " + CLASS + " { void <init>(); }"),
+ extract(edge));
+ }
+
+ private KeepTarget target(KeepItemPattern item) {
+ return KeepTarget.builder().setItem(item).build();
+ }
+
+ private KeepItemPattern classItem(String typeName) {
+ return buildClassItem(typeName).build();
+ }
+
+ private KeepItemPattern.Builder buildClassItem(String typeName) {
+ return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+ }
+
+ private KeepMembersPattern defaultInitializerPattern() {
+ return KeepMembersPattern.builder()
+ .addMethodPattern(
+ KeepMethodPattern.builder()
+ .setNamePattern(KeepMethodNamePattern.initializer())
+ .setParametersPattern(KeepMethodParametersPattern.none())
+ .setReturnTypeVoid()
+ .build())
+ .build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index c8760d8..5db89dc 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -78,6 +79,11 @@
}
@Override
+ public AppView<?> getAppView() {
+ return null;
+ }
+
+ @Override
public void mergeBlocks(BasicBlock kept, BasicBlock removed) {
// Intentionally empty, we don't need to track merging in this allocator.
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java
new file mode 100644
index 0000000..145cb4b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteRemovedCompanionTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MIN_SUPPORTED_VERSION;
+
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+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 MetadataRewriteRemovedCompanionTest extends KotlinMetadataTestBase {
+ private static final String EXPECTED = StringUtils.lines("Hello World!");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}, {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(),
+ getKotlinTestParameters()
+ .withOldCompilersStartingFrom(KOTLINC_1_4_20)
+ .withCompilersStartingFromIncluding(MIN_SUPPORTED_VERSION)
+ .withAllTargetVersions()
+ .build());
+ }
+
+ public MetadataRewriteRemovedCompanionTest(
+ TestParameters parameters, KotlinTestParameters kotlinParameters) {
+ super(kotlinParameters);
+ this.parameters = parameters;
+ }
+
+ private static final KotlinCompileMemoizer companionRemoveJarMap =
+ getCompileMemoizer(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_lib", "lib"));
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion);
+
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataInCompanion_kept() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion))
+ // Keep everything
+ .addKeepRules("-keep class **.companion_remove_lib.** { *; }")
+ .addKeepKotlinMetadata()
+ // To keep ...$Companion structure
+ .addKeepAttributeInnerClassesAndEnclosingMethod()
+ .compile()
+ .writeToZip();
+
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataInCompanion_removedField() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(companionRemoveJarMap.getForConfiguration(kotlinc, targetVersion))
+ // Keep the ClasWithCompanion class.
+ .addKeepKotlinMetadata()
+ .addKeepRules("-keep class **.ClassWithCompanion { void <init>(); void doStuff(); }")
+ .addKeepRules("-keep class **.ClassWithCompanion$Companion")
+ .compile()
+ .writeToZip();
+
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/companion_remove_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG + ".companion_remove_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt
new file mode 100644
index 0000000..d08a2d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_app/main.kt
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.companion_remove_app
+
+import com.android.tools.r8.kotlin.metadata.companion_remove_lib.ClassWithCompanion
+
+fun main() {
+ ClassWithCompanion().doStuff()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt
new file mode 100644
index 0000000..a16595e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/companion_remove_lib/lib.kt
@@ -0,0 +1,14 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.companion_remove_lib
+
+class ClassWithCompanion {
+ fun doStuff() {
+ println(foo)
+ }
+ companion object {
+ val foo: String
+ get() = "Hello World!"
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
new file mode 100644
index 0000000..2e726b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassMembersDefaultCtorTest.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
+
+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.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassMembersDefaultCtorTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A()");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultCfRuntime().build();
+ }
+
+ public KeepClassMembersDefaultCtorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(KeepClassMembersDefaultCtorTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testCompatR8() throws Exception {
+ run(testForR8Compat(parameters.getBackend()), false);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ run(testForR8(parameters.getBackend()), true);
+ }
+
+ @Test
+ public void testPG() throws Exception {
+ run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass()), false);
+ }
+
+ private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, boolean fullMode)
+ throws Exception {
+ return builder
+ .addInnerClasses(KeepClassMembersDefaultCtorTest.class)
+ .addKeepRules("-keepclassmembers class * { <fields>; }")
+ .addKeepClassAndMembersRules(TestClass.class)
+ .addDontObfuscate()
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspectFailure(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(A.class).init(), isAbsent());
+ assertThat(inspector.clazz(A.class).uniqueFieldWithName("y"), isPresent());
+ assertThat(
+ inspector.clazz(A.class).uniqueFieldWithName("x"),
+ fullMode ? isAbsent() : isPresent());
+ })
+ .assertFailureWithErrorThatThrows(NoSuchMethodException.class);
+ }
+
+ static class A {
+
+ public long x = System.nanoTime();
+ public static long y = System.nanoTime();
+
+ public A() {
+ System.out.println("A()");
+ }
+ }
+
+ static class TestClass {
+
+ public static A getA() {
+ // Since TestClass.A is hard kept, the shrinker can't assume anything about the return value.
+ return null;
+ }
+
+ public static void main(String[] args) throws Exception {
+ String name = args.length == 0 ? "A" : null;
+ Class<?> clazz =
+ Class.forName(
+ TestClass.class.getPackage().getName() + ".KeepClassMembersDefaultCtorTest$" + name);
+ Object obj = clazz.getConstructor().newInstance();
+ if (args.length > 0) {
+ // Use the field so we are sure that the keep rule triggers.
+ A a = getA();
+ System.out.println(a.y);
+ System.out.println(a.x);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java
new file mode 100644
index 0000000..54ee56f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/KeepClassesWithMembersDefaultCtorTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
+
+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.ProguardVersion;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepClassesWithMembersDefaultCtorTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A()");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultCfRuntime().build();
+ }
+
+ public KeepClassesWithMembersDefaultCtorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(KeepClassesWithMembersDefaultCtorTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testCompatR8() throws Exception {
+ checkInitKept(run(testForR8Compat(parameters.getBackend())));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ checkInitNotKept(run(testForR8(parameters.getBackend())));
+ }
+
+ @Test
+ public void testPG() throws Exception {
+ checkInitKept(run(testForProguard(ProguardVersion.getLatest()).addDontWarn(getClass())));
+ }
+
+ private TestRunResult<?> run(TestShrinkerBuilder<?, ?, ?, ?, ?> builder) throws Exception {
+ return builder
+ .addInnerClasses(KeepClassesWithMembersDefaultCtorTest.class)
+ .addKeepRules("-keepclasseswithmembers class * { <fields>; }")
+ .addKeepClassAndMembersRules(TestClass.class)
+ .addDontObfuscate()
+ .run(parameters.getRuntime(), TestClass.class);
+ }
+
+ private TestRunResult<?> checkInitKept(TestRunResult<?> result) throws Exception {
+ return result
+ .inspect(inspector -> assertThat(inspector.clazz(A.class).init(), isPresent()))
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private TestRunResult<?> checkInitNotKept(TestRunResult<?> result) throws Exception {
+ return result
+ .inspectFailure(inspector -> assertThat(inspector.clazz(A.class).init(), isAbsent()))
+ .assertFailureWithErrorThatThrows(NoSuchMethodException.class);
+ }
+
+ static class A {
+
+ public long x = System.nanoTime();
+
+ public A() {
+ System.out.println("A()");
+ }
+ }
+
+ static class TestClass {
+
+ public static A getA() {
+ // Since TestClass.A is hard kept, the shrinker can't assume anything about the return value.
+ return null;
+ }
+
+ public static void main(String[] args) throws Exception {
+ String name = args.length == 0 ? "A" : null;
+ Class<?> clazz =
+ Class.forName(
+ TestClass.class.getPackage().getName()
+ + ".KeepClassesWithMembersDefaultCtorTest$"
+ + name);
+ Object obj = clazz.getConstructor().newInstance();
+ if (args.length > 0) {
+ // Use the field so we are sure that the keep rule triggers.
+ A a = getA();
+ System.out.println(a.x);
+ }
+ }
+ }
+}
diff --git a/third_party/opensource-apps/systemui.tar.gz.sha1 b/third_party/opensource-apps/systemui.tar.gz.sha1
index 9d5b06c..4300260 100644
--- a/third_party/opensource-apps/systemui.tar.gz.sha1
+++ b/third_party/opensource-apps/systemui.tar.gz.sha1
@@ -1 +1 @@
-19afeb80f13ccaadddd62d9e1f8f2b1e894f99c8
\ No newline at end of file
+b5b6321177b6f39ed446c5d408d570dc1598f32c
\ No newline at end of file